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
SpacesBeforeTrailingComments: 2
AccessModifierOffset: -1
AlignEscapedNewlinesLeft: true
AlwaysBreakAfterDefinitionReturnType: false
AlwaysBreakBeforeMultilineStrings: false
BreakConstructorInitializersBeforeComma: true
# BreakBeforeBraces: Linux
BreakBeforeBraces: Custom
AccessModifierOffset: -4
ConstructorInitializerAllOnOneLineOrOnePerLine: false
AlignEscapedNewlinesLeft: true
AllowShortFunctionsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: false
DerivePointerBinding: false
AlwaysBreakAfterDefinitionReturnType: false
AlwaysBreakBeforeMultilineStrings: false
BasedOnStyle: Google
BraceWrapping: {
AfterNamespace: 'false'
AfterClass: 'true'
@ -26,5 +17,14 @@ BraceWrapping: {
AfterFunction: 'true'
BeforeCatch: 'false'
}
ColumnLimit: 100
BreakBeforeBraces: Custom
BreakConstructorInitializersBeforeComma: true
ColumnLimit: 80
ConstructorInitializerAllOnOneLineOrOnePerLine: false
DerivePointerBinding: false
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;
// 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)
: settings(&_settings)
@ -53,7 +54,8 @@ Application::Application(Settings &_settings, Paths &_paths)
{
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.pubsub = this->twitch2->pubsub;
@ -117,40 +119,49 @@ void Application::initPubsub()
Log("WHISPER RECEIVED LOL"); //
});
this->twitch.pubsub->signals_.moderation.chatCleared.connect([this](const auto &action) {
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) {
return;
}
this->twitch.pubsub->signals_.moderation.chatCleared.connect(
[this](const auto &action) {
auto chan =
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);
postToThread([chan, msg] { chan->addMessage(msg); });
});
auto msg = Message::createSystemMessage(text);
postToThread([chan, msg] { chan->addMessage(msg); });
});
this->twitch.pubsub->signals_.moderation.modeChanged.connect([this](const auto &action) {
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) {
return;
}
this->twitch.pubsub->signals_.moderation.modeChanged.connect(
[this](const auto &action) {
auto chan =
this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) {
return;
}
QString text = QString("%1 turned %2 %3 mode") //
.arg(action.source.name)
.arg(action.state == ModeChangedAction::State::On ? "on" : "off")
.arg(action.getModeName());
QString text =
QString("%1 turned %2 %3 mode") //
.arg(action.source.name)
.arg(action.state == ModeChangedAction::State::On ? "on"
: "off")
.arg(action.getModeName());
if (action.duration > 0) {
text.append(" (" + QString::number(action.duration) + " seconds)");
}
if (action.duration > 0) {
text.append(" (" + QString::number(action.duration) +
" seconds)");
}
auto msg = Message::createSystemMessage(text);
postToThread([chan, msg] { chan->addMessage(msg); });
});
auto msg = Message::createSystemMessage(text);
postToThread([chan, msg] { chan->addMessage(msg); });
});
this->twitch.pubsub->signals_.moderation.moderationStateChanged.connect(
[this](const auto &action) {
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
auto chan =
this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) {
return;
}
@ -158,48 +169,55 @@ void Application::initPubsub()
QString text;
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 {
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);
postToThread([chan, msg] { chan->addMessage(msg); });
});
this->twitch.pubsub->signals_.moderation.userBanned.connect([&](const auto &action) {
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
this->twitch.pubsub->signals_.moderation.userBanned.connect(
[&](const auto &action) {
auto chan =
this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) {
return;
}
if (chan->isEmpty()) {
return;
}
auto msg = Message::createTimeoutMessage(action);
msg->flags |= Message::PubSub;
auto msg = Message::createTimeoutMessage(action);
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) {
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
this->twitch.pubsub->signals_.moderation.userUnbanned.connect(
[&](const auto &action) {
auto chan =
this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) {
return;
}
if (chan->isEmpty()) {
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();
auto RequestModerationActions = [=]() {
this->twitch.server->pubsub->unlistenAllModerationActions();
// TODO(pajlada): Unlisten to all authed topics instead of only moderation topics
// this->twitch.pubsub->UnlistenAllAuthedTopics();
// TODO(pajlada): Unlisten to all authed topics instead of only
// 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);

View file

@ -77,7 +77,8 @@ private:
void initPubsub();
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()
{
auto t = new T;

View file

@ -54,15 +54,16 @@ void runLoop(NativeMessagingClient &client)
std::cin.read(b.get(), size);
*(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
bool shouldRunBrowserExtensionHost(const QStringList &args)
{
return args.size() > 0 &&
(args[0].startsWith("chrome-extension://") || args[0].endsWith(".json"));
return args.size() > 0 && (args[0].startsWith("chrome-extension://") ||
args[0].endsWith(".json"));
}
void runBrowserExtensionHost()

View file

@ -32,23 +32,28 @@ void installCustomPalette()
darkPalette.setColor(QPalette::Window, QColor(22, 22, 22));
darkPalette.setColor(QPalette::WindowText, 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::AlternateBase, QColor("#444"));
darkPalette.setColor(QPalette::ToolTipBase, 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::Shadow, QColor(20, 20, 20));
darkPalette.setColor(QPalette::Button, QColor(70, 70, 70));
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::Link, 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::Disabled, QPalette::HighlightedText, QColor(127, 127, 127));
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText,
QColor(127, 127, 127));
qApp->setPalette(darkPalette);
}
@ -106,7 +111,8 @@ void runGui(QApplication &a, Paths &paths, Settings &settings)
#endif
// Running file
auto runningPath = paths.miscDirectory + "/running_" + paths.applicationFilePathHash;
auto runningPath =
paths.miscDirectory + "/running_" + paths.applicationFilePathHash;
if (QFile::exists(runningPath)) {
showLastCrashDialog();

View file

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

View file

@ -22,9 +22,10 @@ Channel::Channel(const QString &name, Type type)
, name_(name)
, type_(type)
{
QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout, [this]() {
this->completionModel.clearExpiredStrings(); //
});
QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout,
[this]() {
this->completionModel.clearExpiredStrings(); //
});
this->clearCompletionModelTimer_.start(60 * 1000);
}
@ -65,7 +66,8 @@ void Channel::addMessage(MessagePtr message)
const QString &username = message->loginName;
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);
}
@ -101,17 +103,21 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
break;
}
if (s->flags.HasFlag(Message::Untimeout) && s->timeoutUser == message->timeoutUser) {
if (s->flags.HasFlag(Message::Untimeout) &&
s->timeoutUser == message->timeoutUser) {
break;
}
if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == message->timeoutUser) {
if (message->flags.HasFlag(Message::PubSub) && !s->flags.HasFlag(Message::PubSub)) {
if (s->flags.HasFlag(Message::Timeout) &&
s->timeoutUser == message->timeoutUser) {
if (message->flags.HasFlag(Message::PubSub) &&
!s->flags.HasFlag(Message::PubSub)) {
this->replaceMessage(s, message);
addMessage = false;
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;
break;
}
@ -119,7 +125,8 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
int count = s->count + 1;
MessagePtr replacement(Message::createSystemMessage(
message->searchText + QString(" (") + QString::number(count) + " times)"));
message->searchText + QString(" (") + QString::number(count) +
" times)"));
replacement->timeoutUser = message->timeoutUser;
replacement->count = count;
@ -164,7 +171,8 @@ void Channel::disableAllMessages()
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) {
this->messagesAddedAtStart.invoke(addedMessages);

View file

@ -32,7 +32,8 @@ public:
explicit Channel(const QString &name, Type type);
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 &> messageAppended;
@ -96,7 +97,8 @@ class IndirectChannel
};
public:
IndirectChannel(ChannelPtr channel, Channel::Type type = Channel::Type::Direct)
IndirectChannel(ChannelPtr channel,
Channel::Type type = Channel::Type::Direct)
: data_(new Data(channel, type))
{
}

View file

@ -25,8 +25,10 @@ inline QString qS(const std::string &string)
return QString::fromStdString(string);
}
const Qt::KeyboardModifiers showSplitOverlayModifiers = Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showAddSplitRegions = Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showSplitOverlayModifiers =
Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showAddSplitRegions =
Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - ";

View file

@ -111,26 +111,32 @@ void CompletionModel::refresh()
// User-specific: Twitch Emotes
if (auto account = app->accounts->twitch.getCurrent()) {
for (const auto &emote : account->accessEmotes()->allEmoteNames) {
// XXX: No way to discern between a twitch global emote and sub emote right now
this->addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
// XXX: No way to discern between a twitch global emote and sub
// emote right now
this->addString(emote.string,
TaggedString::Type::TwitchGlobalEmote);
}
}
// // Global: BTTV Global Emotes
// std::vector<QString> &bttvGlobalEmoteCodes = app->emotes->bttv.globalEmoteNames_;
// for (const auto &m : bttvGlobalEmoteCodes) {
// std::vector<QString> &bttvGlobalEmoteCodes =
// app->emotes->bttv.globalEmoteNames_; for (const auto &m :
// bttvGlobalEmoteCodes) {
// this->addString(m, TaggedString::Type::BTTVGlobalEmote);
// }
// // Global: FFZ Global Emotes
// std::vector<QString> &ffzGlobalEmoteCodes = app->emotes->ffz.globalEmoteCodes;
// for (const auto &m : ffzGlobalEmoteCodes) {
// std::vector<QString> &ffzGlobalEmoteCodes =
// app->emotes->ffz.globalEmoteCodes; for (const auto &m :
// ffzGlobalEmoteCodes) {
// this->addString(m, TaggedString::Type::FFZGlobalEmote);
// }
// Channel emotes
if (auto channel = dynamic_cast<TwitchChannel *>(
getApp()->twitch2->getChannelOrEmptyByID(this->channelName_).get())) {
getApp()
->twitch2->getChannelOrEmptyByID(this->channelName_)
.get())) {
auto bttv = channel->accessBttvEmotes();
// auto it = bttv->begin();
// for (const auto &emote : *bttv) {
@ -143,7 +149,8 @@ void CompletionModel::refresh()
// Channel-specific: FFZ Channel Emotes
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
// fourtf: only works with twitch chat
// auto c = ChannelManager::getInstance().getTwitchChannel(this->channelName);
// auto c =
// ChannelManager::getInstance().getTwitchChannel(this->channelName);
// auto usernames = c->getUsernamesForCompletions();
// for (const auto &name : usernames) {
// assert(!name.displayName.isEmpty());
@ -191,9 +199,11 @@ void CompletionModel::addUser(const QString &username)
auto add = [this](const QString &str) {
auto ts = this->createUser(str + " ");
// 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) {
// 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) {
// Replace lowercase version of name with mixed-case version

View file

@ -65,7 +65,8 @@ public:
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);

View file

@ -14,8 +14,8 @@ namespace chatterino {
// bool isValid() const;
// Image *getImage(float scale) const;
// // Link to the emote page i.e. https://www.frankerfacez.com/emoticon/144722-pajaCringe
// QString pageLink;
// // Link to the emote page i.e.
// https://www.frankerfacez.com/emoticon/144722-pajaCringe QString pageLink;
// Image *image1x = 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]))"
"|"
// 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
"(?:\\.(?:[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
//"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}]{2,}))"
"(?:[\\.](?:" +
@ -61,7 +63,8 @@ LinkParser::LinkParser(const QString &unparsedString)
"(?:[/?#]\\S*)?"
"$";
return QRegularExpression(hyperlinkRegExp, QRegularExpression::CaseInsensitiveOption);
return QRegularExpression(hyperlinkRegExp,
QRegularExpression::CaseInsensitiveOption);
}();
this->match_ = linkRegex.match(unparsedString);

View file

@ -30,7 +30,8 @@ QString NetworkData::getHash()
bytes.append(header);
}
QByteArray hashBytes(QCryptographicHash::hash(bytes, QCryptographicHash::Sha256));
QByteArray hashBytes(
QCryptographicHash::hash(bytes, QCryptographicHash::Sha256));
this->hash_ = hashBytes.toHex();
}

View file

@ -11,7 +11,8 @@
namespace chatterino {
NetworkRequest::NetworkRequest(const std::string &url, NetworkRequestType requestType)
NetworkRequest::NetworkRequest(const std::string &url,
NetworkRequestType requestType)
: data(new NetworkData)
, timer(new NetworkTimer)
{
@ -62,7 +63,8 @@ void NetworkRequest::setRawHeader(const char *headerName, const char *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);
}
@ -77,7 +79,8 @@ void NetworkRequest::setTimeout(int 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("Accept", "application/vnd.twitchtv.v5+json");
@ -114,12 +117,14 @@ void NetworkRequest::execute()
} break;
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();
} break;
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();
} break;
@ -152,7 +157,8 @@ Outcome NetworkRequest::tryLoadCachedFile()
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;
}
@ -166,14 +172,16 @@ void NetworkRequest::doRequest()
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 * {
switch (data->requestType_) {
case NetworkRequestType::Get:
return NetworkManager::NaM.get(data->request_);
case NetworkRequestType::Put:
return NetworkManager::NaM.put(data->request_, data->payload_);
return NetworkManager::NaM.put(data->request_,
data->payload_);
case NetworkRequestType::Delete:
return NetworkManager::NaM.deleteResource(data->request_);
@ -221,12 +229,14 @@ void NetworkRequest::doRequest()
};
if (data->caller_ != nullptr) {
QObject::connect(worker, &NetworkWorker::doneUrl, data->caller_, handleReply);
QObject::connect(reply, &QNetworkReply::finished, worker, [worker]() mutable {
emit worker->doneUrl();
QObject::connect(worker, &NetworkWorker::doneUrl, data->caller_,
handleReply);
QObject::connect(reply, &QNetworkReply::finished, worker,
[worker]() mutable {
emit worker->doneUrl();
delete worker;
});
delete worker;
});
} else {
QObject::connect(reply, &QNetworkReply::finished, worker,
[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();
}

View file

@ -12,15 +12,18 @@ namespace chatterino {
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;
// Timer that tracks the timeout
// 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;
// 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;
public:
@ -31,9 +34,11 @@ public:
NetworkRequest(NetworkRequest &&other) = default;
NetworkRequest &operator=(NetworkRequest &&other) = default;
explicit NetworkRequest(const std::string &url,
NetworkRequestType requestType = NetworkRequestType::Get);
explicit NetworkRequest(QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
explicit NetworkRequest(
const std::string &url,
NetworkRequestType requestType = NetworkRequestType::Get);
explicit NetworkRequest(
QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
~NetworkRequest();
@ -50,14 +55,15 @@ public:
void setRawHeader(const char *headerName, const QByteArray &value);
void setRawHeader(const char *headerName, const QString &value);
void setTimeout(int ms);
void makeAuthorizedV5(const QString &clientID, const QString &oauthToken = QString());
void makeAuthorizedV5(const QString &clientID,
const QString &oauthToken = QString());
void execute();
private:
// 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
// "invalid" is specified by the onSuccess callback
// Returns false if the cache file either didn't exist, or it contained
// "invalid" data "invalid" is specified by the onSuccess callback
Outcome tryLoadCachedFile();
void doRequest();

View file

@ -27,11 +27,12 @@ rapidjson::Document NetworkResult::parseRapidJson() const
{
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) {
Log("JSON parse error: {} ({})", rapidjson::GetParseError_En(result.Code()),
result.Offset());
Log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset());
return ret;
}

View file

@ -28,7 +28,8 @@ bool NetworkTimer::isStarted() const
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(worker != nullptr);

View file

@ -59,7 +59,8 @@ public:
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
{
return NullablePtr<const T>(this->element_);

View file

@ -8,7 +8,8 @@ namespace Settings {
template <>
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);
}
@ -20,12 +21,14 @@ struct Deserialize<QString> {
{
if (!value.IsString()) {
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{};
}
try {
return QString::fromUtf8(value.GetString(), value.GetStringLength());
return QString::fromUtf8(value.GetString(),
value.GetStringLength());
} catch (const std::exception &) {
// int x = 5;
} catch (...) {

View file

@ -88,7 +88,8 @@ template <typename TVectorItem>
class UnsortedSignalVector : public BaseSignalVector<TVectorItem>
{
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();
if (index == -1) {
@ -115,11 +116,13 @@ template <typename TVectorItem, typename Compare>
class SortedSignalVector : public BaseSignalVector<TVectorItem>
{
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();
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();
this->vector_.insert(it, item);

View file

@ -11,7 +11,8 @@
namespace chatterino {
template <typename TVectorItem>
class SignalVectorModel : public QAbstractTableModel, pajlada::Signals::SignalHolder
class SignalVectorModel : public QAbstractTableModel,
pajlada::Signals::SignalHolder
{
public:
SignalVectorModel(int columnCount, QObject *parent = nullptr)
@ -43,7 +44,8 @@ public:
index = this->beforeInsert(args.item, row, 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();
};
@ -65,7 +67,8 @@ public:
assert(row >= 0 && row <= this->rows_.size());
// 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->rows_.erase(this->rows_.begin() + row);
@ -103,15 +106,18 @@ public:
QVariant data(const QModelIndex &index, int role) const override
{
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);
}
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();
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];
@ -124,15 +130,16 @@ public:
this->vector_->removeItem(vecRow, this);
assert(this->rows_[row].original);
TVectorItem item =
this->getItemFromRow(this->rows_[row].items, this->rows_[row].original.get());
TVectorItem item = this->getItemFromRow(
this->rows_[row].items, this->rows_[row].original.get());
this->vector_->insertItem(item, vecRow, this);
}
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) {
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
{
if (orientation != Qt::Horizontal) {
@ -162,14 +170,16 @@ public:
Qt::ItemFlags flags(const QModelIndex &index) const override
{
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();
}
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];
}
@ -204,20 +214,23 @@ protected:
const TVectorItem &original) = 0;
// 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)
{
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,
const QVariant &value, int role)
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
int column, const QVariant &value, int role)
{
}
@ -226,7 +239,8 @@ protected:
assert(index >= 0 && index <= this->rows_.size());
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();
}

View file

@ -7,7 +7,8 @@ namespace chatterino {
AccountController::AccountController()
{
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) {

View file

@ -28,7 +28,8 @@ public:
TwitchAccountManager twitch;
private:
SortedSignalVector<std::shared_ptr<Account>, SharedPtrElementLess<Account>> accounts_;
SortedSignalVector<std::shared_ptr<Account>, SharedPtrElementLess<Account>>
accounts_;
};
} // namespace chatterino

View file

@ -10,8 +10,8 @@ AccountModel::AccountModel(QObject *parent)
}
// turn a vector item into a model row
std::shared_ptr<Account> AccountModel::getItemFromRow(std::vector<QStandardItem *> &,
const std::shared_ptr<Account> &original)
std::shared_ptr<Account> AccountModel::getItemFromRow(
std::vector<QStandardItem *> &, const std::shared_ptr<Account> &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,
std::vector<QStandardItem *> &row, int proposedIndex)
std::vector<QStandardItem *> &row,
int proposedIndex)
{
if (this->categoryCount_[item->getCategory()]++ == 0) {
auto row = this->createRow();

View file

@ -18,17 +18,20 @@ public:
protected:
// turn a vector item into a model row
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
virtual void getRowFromItem(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row) override;
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,
std::vector<QStandardItem *> &row, int index) override;
std::vector<QStandardItem *> &row,
int index) override;
friend class AccountController;

View file

@ -18,12 +18,13 @@
#include <QFile>
#include <QRegularExpression>
#define TWITCH_DEFAULT_COMMANDS \
{ \
"/help", "/w", "/me", "/disconnect", "/mods", "/color", "/ban", "/unban", "/timeout", \
"/untimeout", "/slow", "/slowoff", "/r9kbeta", "/r9kbetaoff", "/emoteonly", \
"/emoteonlyoff", "/clear", "/subscribers", "/subscribersoff", "/followers", \
"/followersoff" \
#define TWITCH_DEFAULT_COMMANDS \
{ \
"/help", "/w", "/me", "/disconnect", "/mods", "/color", "/ban", \
"/unban", "/timeout", "/untimeout", "/slow", "/slowoff", \
"/r9kbeta", "/r9kbetaoff", "/emoteonly", "/emoteonlyoff", \
"/clear", "/subscribers", "/subscribersoff", "/followers", \
"/followersoff" \
}
namespace chatterino {
@ -77,7 +78,8 @@ void CommandController::save()
{
QFile textFile(this->filePath_);
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;
}
@ -96,7 +98,8 @@ CommandModel *CommandController::createModel(QObject *parent)
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);
Command command;
@ -122,11 +125,13 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
MessageBuilder b;
b.emplace<TimestampElement>();
b.emplace<TextElement>(app->accounts->twitch.getCurrent()->getUserName(),
MessageElement::Text, MessageColor::Text,
FontStyle::ChatMediumBold);
b.emplace<TextElement>(
app->accounts->twitch.getCurrent()->getUserName(),
MessageElement::Text, MessageColor::Text,
FontStyle::ChatMediumBold);
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);
QString rest = "";
@ -144,7 +149,9 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
if (getSettings()->inlineWhispers) {
app->twitch.server->forEachChannel(
[&b](ChannelPtr _channel) { _channel->addMessage(b.getMessage()); });
[&b](ChannelPtr _channel) {
_channel->addMessage(b.getMessage());
});
}
return "";
@ -165,15 +172,17 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
} else if (commandName == "/uptime") {
const auto &streamStatus = twitchChannel->accessStreamStatus();
QString messageText =
streamStatus->live ? streamStatus->uptime : "Channel is not live.";
QString messageText = streamStatus->live
? streamStatus->uptime
: "Channel is not live.";
channel->addMessage(Message::createSystemMessage(messageText));
return "";
} else if (commandName == "/ignore") {
if (words.size() < 2) {
channel->addMessage(Message::createSystemMessage("Usage: /ignore [user]"));
channel->addMessage(
Message::createSystemMessage("Usage: /ignore [user]"));
return "";
}
auto app = getApp();
@ -182,19 +191,21 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
auto target = words.at(1);
if (user->isAnon()) {
channel->addMessage(
Message::createSystemMessage("You must be logged in to ignore someone"));
channel->addMessage(Message::createSystemMessage(
"You must be logged in to ignore someone"));
return "";
}
user->ignore(target, [channel](auto resultCode, const QString &message) {
user->ignore(target, [channel](auto resultCode,
const QString &message) {
channel->addMessage(Message::createSystemMessage(message));
});
return "";
} else if (commandName == "/unignore") {
if (words.size() < 2) {
channel->addMessage(Message::createSystemMessage("Usage: /unignore [user]"));
channel->addMessage(Message::createSystemMessage(
"Usage: /unignore [user]"));
return "";
}
auto app = getApp();
@ -203,19 +214,21 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
auto target = words.at(1);
if (user->isAnon()) {
channel->addMessage(
Message::createSystemMessage("You must be logged in to ignore someone"));
channel->addMessage(Message::createSystemMessage(
"You must be logged in to ignore someone"));
return "";
}
user->unignore(target, [channel](auto resultCode, const QString &message) {
user->unignore(target, [channel](auto resultCode,
const QString &message) {
channel->addMessage(Message::createSystemMessage(message));
});
return "";
} else if (commandName == "/follow") {
if (words.size() < 2) {
channel->addMessage(Message::createSystemMessage("Usage: /follow [user]"));
channel->addMessage(
Message::createSystemMessage("Usage: /follow [user]"));
return "";
}
auto app = getApp();
@ -224,27 +237,29 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
auto target = words.at(1);
if (user->isAnon()) {
channel->addMessage(
Message::createSystemMessage("You must be logged in to follow someone"));
channel->addMessage(Message::createSystemMessage(
"You must be logged in to follow someone"));
return "";
}
TwitchApi::findUserId(target, [user, channel, target](QString userId) {
if (userId.isEmpty()) {
channel->addMessage(Message::createSystemMessage(
"User " + target + " could not be followed!"));
return;
}
user->followUser(userId, [channel, target]() {
channel->addMessage(
Message::createSystemMessage("You successfully followed " + target));
TwitchApi::findUserId(
target, [user, channel, target](QString userId) {
if (userId.isEmpty()) {
channel->addMessage(Message::createSystemMessage(
"User " + target + " could not be followed!"));
return;
}
user->followUser(userId, [channel, target]() {
channel->addMessage(Message::createSystemMessage(
"You successfully followed " + target));
});
});
});
return "";
} else if (commandName == "/unfollow") {
if (words.size() < 2) {
channel->addMessage(Message::createSystemMessage("Usage: /unfollow [user]"));
channel->addMessage(Message::createSystemMessage(
"Usage: /unfollow [user]"));
return "";
}
auto app = getApp();
@ -253,28 +268,29 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
auto target = words.at(1);
if (user->isAnon()) {
channel->addMessage(
Message::createSystemMessage("You must be logged in to follow someone"));
channel->addMessage(Message::createSystemMessage(
"You must be logged in to follow someone"));
return "";
}
TwitchApi::findUserId(target, [user, channel, target](QString userId) {
if (userId.isEmpty()) {
channel->addMessage(Message::createSystemMessage(
"User " + target + " could not be followed!"));
return;
}
user->unfollowUser(userId, [channel, target]() {
channel->addMessage(
Message::createSystemMessage("You successfully unfollowed " + target));
TwitchApi::findUserId(
target, [user, channel, target](QString userId) {
if (userId.isEmpty()) {
channel->addMessage(Message::createSystemMessage(
"User " + target + " could not be followed!"));
return;
}
user->unfollowUser(userId, [channel, target]() {
channel->addMessage(Message::createSystemMessage(
"You successfully unfollowed " + target));
});
});
});
return "";
} else if (commandName == "/logs") {
if (words.size() < 2) {
channel->addMessage(
Message::createSystemMessage("Usage: /logs [user] (channel)"));
channel->addMessage(Message::createSystemMessage(
"Usage: /logs [user] (channel)"));
return "";
}
auto app = getApp();
@ -293,7 +309,8 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
if (words.at(2).at(0) == "#") {
channelName = words.at(2).mid(1);
}
auto logsChannel = app->twitch.server->getChannelOrEmpty(channelName);
auto logsChannel =
app->twitch.server->getChannelOrEmpty(channelName);
if (logsChannel == nullptr) {
} else {
logs->setInfo(logsChannel, target);
@ -319,7 +336,8 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
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;
@ -331,13 +349,15 @@ QString CommandController::execCustomCommand(const QStringList &words, const Com
int matchOffset = 0;
while (true) {
QRegularExpressionMatch match = parseCommand.match(command.func, matchOffset);
QRegularExpressionMatch match =
parseCommand.match(command.func, matchOffset);
if (!match.hasMatch()) {
break;
}
result += command.func.mid(lastCaptureEnd, match.capturedStart() - lastCaptureEnd + 1);
result += command.func.mid(lastCaptureEnd,
match.capturedStart() - lastCaptureEnd + 1);
lastCaptureEnd = match.capturedEnd();
matchOffset = lastCaptureEnd - 1;

View file

@ -22,7 +22,8 @@ class CommandController final : public Singleton
public:
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();
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
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
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]->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]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
row[1]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
Qt::ItemIsEditable);
}
} // namespace chatterino

View file

@ -19,7 +19,8 @@ protected:
const Command &command) override;
// 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;
};

View file

@ -15,8 +15,9 @@ class HighlightBlacklistModel : public SignalVectorModel<HighlightBlacklistUser>
protected:
// turn a vector item into a model row
virtual HighlightBlacklistUser getItemFromRow(std::vector<QStandardItem *> &row,
const HighlightBlacklistUser &original) override;
virtual HighlightBlacklistUser getItemFromRow(
std::vector<QStandardItem *> &row,
const HighlightBlacklistUser &original) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const HighlightBlacklistUser &item,

View file

@ -16,14 +16,16 @@ class HighlightBlacklistUser
public:
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)
: pattern_(pattern)
, isRegex_(isRegex)
, regex_(isRegex ? pattern : "", QRegularExpression::CaseInsensitiveOption |
QRegularExpression::UseUnicodePropertiesOption)
, regex_(isRegex ? pattern : "",
QRegularExpression::CaseInsensitiveOption |
QRegularExpression::UseUnicodePropertiesOption)
{
}

View file

@ -25,12 +25,14 @@ void HighlightController::initialize(Settings &settings, Paths &paths)
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.delayedItemsChanged.connect(
[this] { this->blacklistSetting_.setValue(this->blacklistedUsers.getVector()); });
this->blacklistedUsers.delayedItemsChanged.connect([this] {
this->blacklistSetting_.setValue(this->blacklistedUsers.getVector());
});
}
HighlightModel *HighlightController::createModel(QObject *parent)
@ -61,7 +63,8 @@ bool HighlightController::isHighlightedUser(const QString &username)
return false;
}
HighlightBlacklistModel *HighlightController::createBlacklistModel(QObject *parent)
HighlightBlacklistModel *HighlightController::createBlacklistModel(
QObject *parent)
{
auto *model = new HighlightBlacklistModel(parent);
model->init(&this->blacklistedUsers);
@ -71,7 +74,8 @@ HighlightBlacklistModel *HighlightController::createBlacklistModel(QObject *pare
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) {
if (blacklistedUser.isMatch(username)) {
return true;

View file

@ -13,18 +13,20 @@ HighlightModel::HighlightModel(QObject *parent)
}
// turn a vector item into a model row
HighlightPhrase HighlightModel::getItemFromRow(std::vector<QStandardItem *> &row,
const HighlightPhrase &original)
HighlightPhrase HighlightModel::getItemFromRow(
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
{
// key, alert, sound, regex
return HighlightPhrase{
row[0]->data(Qt::DisplayRole).toString(), row[1]->data(Qt::CheckStateRole).toBool(),
row[2]->data(Qt::CheckStateRole).toBool(), row[3]->data(Qt::CheckStateRole).toBool()};
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
row[1]->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
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());
setBoolItem(row[1], item.getAlert());
@ -35,31 +37,38 @@ void HighlightModel::getRowFromItem(const HighlightPhrase &item, std::vector<QSt
void HighlightModel::afterInit()
{
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);
setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(), true, false);
setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(), true, false);
setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(),
true, false);
setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(),
true, false);
row[3]->setFlags(0);
this->insertCustomRow(row, 0);
}
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row, int column,
const QVariant &value, int role)
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
int column, const QVariant &value,
int role)
{
switch (column) {
case 0: {
if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightsSelf.setValue(value.toBool());
getApp()->settings->enableHighlightsSelf.setValue(
value.toBool());
}
} break;
case 1: {
if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightTaskbar.setValue(value.toBool());
getApp()->settings->enableHighlightTaskbar.setValue(
value.toBool());
}
} break;
case 2: {
if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightSound.setValue(value.toBool());
getApp()->settings->enableHighlightSound.setValue(
value.toBool());
}
} break;
case 3: {

View file

@ -15,8 +15,9 @@ class HighlightModel : public SignalVectorModel<HighlightPhrase>
protected:
// turn a vector item into a model row
virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row,
const HighlightPhrase &original) override;
virtual HighlightPhrase getItemFromRow(
std::vector<QStandardItem *> &row,
const HighlightPhrase &original) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const HighlightPhrase &item,
@ -24,8 +25,9 @@ protected:
virtual void afterInit() override;
virtual void customRowSetData(const std::vector<QStandardItem *> &row, int column,
const QVariant &value, int role) override;
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
int column, const QVariant &value,
int role) override;
friend class HighlightController;
};

View file

@ -14,16 +14,20 @@ class HighlightPhrase
public:
bool operator==(const HighlightPhrase &other) const
{
return std::tie(this->pattern_, this->sound_, this->alert_, this->isRegex_) ==
std::tie(other.pattern_, other.sound_, other.alert_, other.isRegex_);
return std::tie(this->pattern_, this->sound_, this->alert_,
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)
, alert_(alert)
, sound_(sound)
, isRegex_(isRegex)
, regex_(isRegex_ ? pattern : "\\b" + QRegularExpression::escape(pattern) + "\\b",
, regex_(isRegex_ ? pattern
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
QRegularExpression::CaseInsensitiveOption |
QRegularExpression::UseUnicodePropertiesOption)
{

View file

@ -13,14 +13,15 @@ UserHighlightModel::UserHighlightModel(QObject *parent)
}
// turn vector item into model row
HighlightPhrase UserHighlightModel::getItemFromRow(std::vector<QStandardItem *> &row,
const HighlightPhrase &original)
HighlightPhrase UserHighlightModel::getItemFromRow(
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
{
// key, regex
return HighlightPhrase{
row[0]->data(Qt::DisplayRole).toString(), row[1]->data(Qt::CheckStateRole).toBool(),
row[2]->data(Qt::CheckStateRole).toBool(), row[3]->data(Qt::CheckStateRole).toBool()};
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
row[1]->data(Qt::CheckStateRole).toBool(),
row[2]->data(Qt::CheckStateRole).toBool(),
row[3]->data(Qt::CheckStateRole).toBool()};
}
// row into vector item

View file

@ -15,8 +15,9 @@ class UserHighlightModel : public SignalVectorModel<HighlightPhrase>
protected:
// vector into model row
virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row,
const HighlightPhrase &original) override;
virtual HighlightPhrase getItemFromRow(
std::vector<QStandardItem *> &row,
const HighlightPhrase &original) override;
virtual void getRowFromItem(const HighlightPhrase &item,
std::vector<QStandardItem *> &row) override;

View file

@ -25,7 +25,8 @@ public:
private:
bool initialized_ = false;
ChatterinoSetting<std::vector<IgnorePhrase>> ignoresSetting_ = {"/ignore/phrases"};
ChatterinoSetting<std::vector<IgnorePhrase>> ignoresSetting_ = {
"/ignore/phrases"};
};
} // 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
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());
setBoolItem(row[1], item.isRegex());

View file

@ -16,13 +16,15 @@ class IgnorePhrase
public:
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)
: pattern_(pattern)
, isRegex_(isRegex)
, regex_(isRegex_ ? pattern : "\\b" + QRegularExpression::escape(pattern) + "\\b",
, regex_(isRegex_ ? pattern
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
QRegularExpression::CaseInsensitiveOption |
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)
// : _isImage(false)
// , image(nullptr)
@ -55,10 +56,12 @@ ModerationAction::ModerationAction(const QString &action)
// line1 = this->line1_;
// line2 = this->line2_;
// } else {
// this->_moderationActions.emplace_back(app->resources->buttonTimeout, str);
// this->_moderationActions.emplace_back(app->resources->buttonTimeout,
// str);
// }
} else if (action.startsWith("/ban ")) {
this->image_ = Image::fromNonOwningPixmap(&getApp()->resources->buttons.ban);
this->image_ =
Image::fromNonOwningPixmap(&getApp()->resources->buttons.ban);
} else {
QString xD = action;

View file

@ -11,8 +11,8 @@ ModerationActionModel ::ModerationActionModel(QObject *parent)
}
// turn a vector item into a model row
ModerationAction ModerationActionModel::getItemFromRow(std::vector<QStandardItem *> &row,
const ModerationAction &original)
ModerationAction ModerationActionModel::getItemFromRow(
std::vector<QStandardItem *> &row, const ModerationAction &original)
{
return ModerationAction(row[0]->data(Qt::DisplayRole).toString());
}

View file

@ -16,8 +16,9 @@ public:
protected:
// turn a vector item into a model row
virtual ModerationAction getItemFromRow(std::vector<QStandardItem *> &row,
const ModerationAction &original) override;
virtual ModerationAction getItemFromRow(
std::vector<QStandardItem *> &row,
const ModerationAction &original) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const ModerationAction &item,

View file

@ -25,7 +25,8 @@ public:
ModerationActionModel *createModel(QObject *parent);
private:
ChatterinoSetting<std::vector<ModerationAction>> setting_ = {"/moderation/actions"};
ChatterinoSetting<std::vector<ModerationAction>> setting_ = {
"/moderation/actions"};
bool initialized_ = false;
};

View file

@ -4,7 +4,8 @@
namespace chatterino {
TaggedUser::TaggedUser(ProviderId provider, const QString &name, const QString &id)
TaggedUser::TaggedUser(ProviderId provider, const QString &name,
const QString &id)
: providerId_(provider)
, name_(name)
, 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
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());
}
@ -27,14 +28,18 @@ void TaggedUsersModel::getRowFromItem(const TaggedUser &item, std::vector<QStand
void TaggedUsersModel::afterInit()
{
// std::vector<QStandardItem *> row = this->createRow();
// setBoolItem(row[0], getApp()->settings->enableHighlightsSelf.getValue(), true,
// false); row[0]->setData("Your username (automatic)", Qt::DisplayRole);
// setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(), true,
// false); setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(),
// true, false); row[3]->setFlags(0); this->insertCustomRow(row, 0);
// setBoolItem(row[0],
// getApp()->settings->enableHighlightsSelf.getValue(), true, false);
// row[0]->setData("Your username (automatic)", Qt::DisplayRole);
// setBoolItem(row[1],
// 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)
//{
// switch (column) {

View file

@ -17,12 +17,15 @@ protected:
const TaggedUser &original) override;
// 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 customRowSetData(const std::vector<QStandardItem *> &row, int column,
// const QVariant &value, int role) override;
// virtual void customRowSetData(const std::vector<QStandardItem *> &row,
// int column,
// const QVariant &value, int role)
// override;
friend class TaggedUsersController;
};

View file

@ -29,7 +29,8 @@ public:
~BenchmarkGuard()
{
qDebug() << this->name << float(timer.nsecsElapsed()) / 1000000.0f << "ms";
qDebug() << this->name << float(timer.nsecsElapsed()) / 1000000.0f
<< "ms";
}
qreal getElapsedMs()

View file

@ -12,9 +12,10 @@ int main(int argc, char **argv)
{
QApplication a(argc, argv);
// convert char[][] to QStringList
// convert char** to 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
if (shouldRunBrowserExtensionHost(args)) {

View file

@ -85,7 +85,8 @@ std::vector<Frame> readFrames(QImageReader &reader, const Url &url)
std::vector<Frame> frames;
if (reader.imageCount() <= 0) {
Log("Error while reading image {}: '{}'", url.string, reader.errorString());
Log("Error while reading image {}: '{}'", url.string,
reader.errorString());
return frames;
}
@ -100,7 +101,8 @@ std::vector<Frame> readFrames(QImageReader &reader, const Url &url)
}
if (frames.size() != 0) {
Log("Error while reading image {}: '{}'", url.string, reader.errorString());
Log("Error while reading image {}: '{}'", url.string,
reader.errorString());
}
return frames;

View file

@ -47,7 +47,8 @@ class Image : public std::enable_shared_from_this<Image>, boost::noncopyable
{
public:
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 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)
, imageX2_(image2)
, imageX3_(image3)

View file

@ -16,7 +16,8 @@ namespace chatterino {
//
// Explanation:
// - 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,
// 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
@ -41,7 +42,8 @@ public:
{
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->resize(this->chunkSize_);
this->chunks_->push_back(chunk);
@ -60,8 +62,8 @@ public:
// still space in the last chunk
if (lastChunk->size() <= this->lastChunkEnd_) {
// create new chunk vector
ChunkVector newVector =
std::make_shared<std::vector<std::shared_ptr<std::vector<T>>>>();
ChunkVector newVector = std::make_shared<
std::vector<std::shared_ptr<std::vector<T>>>>();
// copy chunks
for (Chunk &chunk : *this->chunks_) {
@ -93,8 +95,8 @@ public:
std::lock_guard<std::mutex> lock(this->mutex_);
// create new vector to clone chunks into
ChunkVector newChunks =
std::make_shared<std::vector<std::shared_ptr<std::vector<T>>>>();
ChunkVector newChunks = std::make_shared<
std::vector<std::shared_ptr<std::vector<T>>>>();
newChunks->resize(this->chunks_->size());
@ -142,7 +144,8 @@ public:
Chunk &chunk = this->chunks_->at(i);
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++) {
if (chunk->at(j) == item) {
@ -176,7 +179,8 @@ public:
Chunk &chunk = this->chunks_->at(i);
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++) {
if (x == index) {
@ -204,8 +208,9 @@ public:
{
std::lock_guard<std::mutex> lock(this->mutex_);
return LimitedQueueSnapshot<T>(this->chunks_, this->limit_ - this->space(),
this->firstChunkOffset_, this->lastChunkEnd_);
return LimitedQueueSnapshot<T>(
this->chunks_, this->limit_ - this->space(),
this->firstChunkOffset_, this->lastChunkEnd_);
}
private:
@ -238,8 +243,8 @@ private:
// need to delete the first chunk
if (this->firstChunkOffset_ == this->chunks_->front()->size() - 1) {
// copy the chunk vector
ChunkVector newVector =
std::make_shared<std::vector<std::shared_ptr<std::vector<T>>>>();
ChunkVector newVector = std::make_shared<
std::vector<std::shared_ptr<std::vector<T>>>>();
// delete first chunk
bool first = true;

View file

@ -12,8 +12,9 @@ class LimitedQueueSnapshot
public:
LimitedQueueSnapshot() = default;
LimitedQueueSnapshot(std::shared_ptr<std::vector<std::shared_ptr<std::vector<T>>>> chunks,
size_t length, size_t firstChunkOffset, size_t lastChunkEnd)
LimitedQueueSnapshot(
std::shared_ptr<std::vector<std::shared_ptr<std::vector<T>>>> chunks,
size_t length, size_t firstChunkOffset, size_t lastChunkEnd)
: chunks_(chunks)
, length_(length)
, firstChunkOffset_(firstChunkOffset)

View file

@ -33,7 +33,8 @@ MessagePtr Message::createSystemMessage(const QString &text)
MessagePtr message(new Message);
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::DoNotTriggerNotification;
message->searchText = text;
@ -46,7 +47,8 @@ MessagePtr Message::createMessage(const QString &text)
MessagePtr message(new Message);
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;
return message;
@ -96,8 +98,10 @@ QString makeDuration(int timeoutSeconds)
} // namespace
MessagePtr Message::createTimeoutMessage(const QString &username, const QString &durationInSeconds,
const QString &reason, bool multipleTimes)
MessagePtr Message::createTimeoutMessage(const QString &username,
const QString &durationInSeconds,
const QString &reason,
bool multipleTimes)
{
QString text;
@ -135,7 +139,8 @@ MessagePtr Message::createTimeoutMessage(const QString &username, const QString
return message;
}
MessagePtr Message::createTimeoutMessage(const BanAction &action, uint32_t count)
MessagePtr Message::createTimeoutMessage(const BanAction &action,
uint32_t count)
{
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;
return msg;
@ -206,7 +212,8 @@ MessagePtr Message::createUntimeoutMessage(const UnbanAction &action)
.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;
return msg;

View file

@ -68,13 +68,14 @@ public:
static std::shared_ptr<Message> createSystemMessage(const QString &text);
static std::shared_ptr<Message> createMessage(const QString &text);
static std::shared_ptr<Message> createTimeoutMessage(const QString &username,
const QString &durationInSeconds,
const QString &reason, bool multipleTimes);
static std::shared_ptr<Message> createTimeoutMessage(
const QString &username, const QString &durationInSeconds,
const QString &reason, bool multipleTimes);
static std::shared_ptr<Message> createTimeoutMessage(const BanAction &action,
uint32_t count = 1);
static std::shared_ptr<Message> createUntimeoutMessage(const UnbanAction &action);
static std::shared_ptr<Message> createTimeoutMessage(
const BanAction &action, uint32_t count = 1);
static std::shared_ptr<Message> createUntimeoutMessage(
const UnbanAction &action);
};
using MessagePtr = std::shared_ptr<Message>;

View file

@ -47,9 +47,12 @@ QString MessageBuilder::matchLink(const QString &string)
{
LinkParser linkParser(string);
static QRegularExpression httpRegex("\\bhttps?://", QRegularExpression::CaseInsensitiveOption);
static QRegularExpression ftpRegex("\\bftps?://", QRegularExpression::CaseInsensitiveOption);
static QRegularExpression spotifyRegex("\\bspotify:", QRegularExpression::CaseInsensitiveOption);
static QRegularExpression httpRegex(
"\\bhttps?://", QRegularExpression::CaseInsensitiveOption);
static QRegularExpression ftpRegex(
"\\bftps?://", QRegularExpression::CaseInsensitiveOption);
static QRegularExpression spotifyRegex(
"\\bspotify:", QRegularExpression::CaseInsensitiveOption);
if (!linkParser.hasMatch()) {
return QString();
@ -57,7 +60,8 @@ QString MessageBuilder::matchLink(const QString &string)
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://");
}

View file

@ -23,7 +23,8 @@ public:
template <typename T, typename... 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)...);
this->append(element);

View file

@ -67,14 +67,15 @@ ImageElement::ImageElement(ImagePtr image, MessageElement::Flags flags)
// this->setTooltip(image->getTooltip());
}
void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags)
void ImageElement::addToContainer(MessageLayoutContainer &container,
MessageElement::Flags flags)
{
if (flags & this->getFlags()) {
auto size = QSize(this->image_->width() * container.getScale(),
this->image_->height() * container.getScale());
container.addElement(
(new ImageLayoutElement(*this, this->image_, size))->setLink(this->getLink()));
container.addElement((new ImageLayoutElement(*this, this->image_, size))
->setLink(this->getLink()));
}
}
@ -83,7 +84,8 @@ EmoteElement::EmoteElement(const EmotePtr &emote, MessageElement::Flags flags)
: MessageElement(flags)
, 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);
}
@ -93,7 +95,8 @@ EmotePtr EmoteElement::getEmote() const
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 & MessageElement::EmoteImages) {
@ -103,11 +106,12 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElem
auto size = QSize(int(container.getScale() * image->width()),
int(container.getScale() * image->height()));
container.addElement(
(new ImageLayoutElement(*this, image, size))->setLink(this->getLink()));
container.addElement((new ImageLayoutElement(*this, image, size))
->setLink(this->getLink()));
} else {
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();
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_) {
auto getTextLayoutElement = [&](QString text, int width, bool trailingSpace) {
auto getTextLayoutElement = [&](QString text, int width,
bool trailingSpace) {
QColor color = this->color_.getColor(*app->themes);
app->themes->normalizeColor(color);
auto e = (new TextLayoutElement(*this, text, QSize(width, metrics.height()), color,
this->style_, container.getScale()))
auto e = (new TextLayoutElement(
*this, text, QSize(width, metrics.height()),
color, this->style_, container.getScale()))
->setLink(this->getLink());
e->setTrailingSpace(trailingSpace);
return e;
@ -152,8 +160,8 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
// see if the text fits in the current line
if (container.fitsInLine(word.width)) {
container.addElementNoLineBreak(
getTextLayoutElement(word.text, word.width, this->hasTrailingSpace()));
container.addElementNoLineBreak(getTextLayoutElement(
word.text, word.width, this->hasTrailingSpace()));
continue;
}
@ -162,8 +170,8 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
container.breakLine();
if (container.fitsInLine(word.width)) {
container.addElementNoLineBreak(
getTextLayoutElement(word.text, word.width, this->hasTrailingSpace()));
container.addElementNoLineBreak(getTextLayoutElement(
word.text, word.width, this->hasTrailingSpace()));
continue;
}
}
@ -178,8 +186,8 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
int charWidth = metrics.width(text[i]);
if (!container.fitsInLine(width + charWidth)) {
container.addElementNoLineBreak(
getTextLayoutElement(text.mid(wordStart, i - wordStart), width, false));
container.addElementNoLineBreak(getTextLayoutElement(
text.mid(wordStart, i - wordStart), width, false));
container.breakLine();
wordStart = i;
@ -194,8 +202,8 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
width += charWidth;
}
container.addElement(
getTextLayoutElement(text.mid(wordStart), width, this->hasTrailingSpace()));
container.addElement(getTextLayoutElement(
text.mid(wordStart), width, this->hasTrailingSpace()));
container.breakLine();
}
}
@ -230,7 +238,8 @@ TextElement *TimestampElement::formatTime(const QTime &time)
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
@ -243,15 +252,19 @@ void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
MessageElement::Flags flags)
{
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()) {
container.addElement((new ImageLayoutElement(*this, image.get(), size))
->setLink(Link(Link::UserAction, action.getAction())));
container.addElement(
(new ImageLayoutElement(*this, image.get(), size))
->setLink(Link(Link::UserAction, action.getAction())));
} else {
container.addElement(
(new TextIconLayoutElement(*this, action.getLine1(), action.getLine2(),
(new TextIconLayoutElement(*this, action.getLine1(),
action.getLine2(),
container.getScale(), size))
->setLink(Link(Link::UserAction, action.getAction())));
}

View file

@ -72,10 +72,11 @@ public:
// - Chatterino top donator badge
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 |
BadgeChatterino,
Badges = BadgeGlobalAuthority | BadgeChannelAuthority |
BadgeSubscription | BadgeVanity | BadgeChatterino,
ChannelName = (1 << 19),
@ -89,7 +90,8 @@ public:
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),
// used for dynamic bold usernames
@ -100,8 +102,9 @@ public:
LowercaseLink = (1 << 29),
OriginalLink = (1 << 30),
Default = Timestamp | Badges | Username | BitsStatic | FfzEmoteImage | BttvEmoteImage |
TwitchEmoteImage | BitsAmount | Text | AlwaysShow,
Default = Timestamp | Badges | Username | BitsStatic | FfzEmoteImage |
BttvEmoteImage | TwitchEmoteImage | BitsAmount | Text |
AlwaysShow,
};
enum UpdateFlags : char {
@ -121,7 +124,8 @@ public:
bool hasTrailingSpace() const;
Flags getFlags() const;
virtual void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) = 0;
virtual void addToContainer(MessageLayoutContainer &container,
MessageElement::Flags flags) = 0;
protected:
MessageElement(Flags flags);
@ -139,7 +143,8 @@ class ImageElement : public MessageElement
public:
ImageElement(ImagePtr image, MessageElement::Flags flags);
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
void addToContainer(MessageLayoutContainer &container,
MessageElement::Flags flags) override;
private:
ImagePtr image_;
@ -154,7 +159,8 @@ public:
FontStyle style = FontStyle::ChatMedium);
~TextElement() override = default;
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
void addToContainer(MessageLayoutContainer &container,
MessageElement::Flags flags) override;
private:
MessageColor color_;
@ -175,7 +181,8 @@ class EmoteElement : public MessageElement
public:
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;
private:
@ -190,7 +197,8 @@ public:
TimestampElement(QTime time_ = QTime::currentTime());
~TimestampElement() override = default;
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
void addToContainer(MessageLayoutContainer &container,
MessageElement::Flags flags) override;
TextElement *formatTime(const QTime &time);
@ -200,14 +208,15 @@ private:
QString format_;
};
// adds all the custom moderation buttons, adds a variable amount of items depending on settings
// fourtf: implement
// adds all the custom moderation buttons, adds a variable amount of items
// depending on settings fourtf: implement
class TwitchModerationElement : public MessageElement
{
public:
TwitchModerationElement();
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
void addToContainer(MessageLayoutContainer &container,
MessageElement::Flags flags) override;
};
} // namespace chatterino

View file

@ -26,7 +26,8 @@ struct SelectionItem {
if (this->messageIndex < b.messageIndex) {
return true;
}
if (this->messageIndex == b.messageIndex && this->charIndex < b.charIndex) {
if (this->messageIndex == b.messageIndex &&
this->charIndex < b.charIndex) {
return true;
}
return false;
@ -39,7 +40,8 @@ struct SelectionItem {
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
@ -74,7 +76,8 @@ struct Selection {
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 ||
(_flags & MessageElement::ModeratorTools &&
!(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);
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);
}
@ -123,7 +125,8 @@ void MessageLayout::actuallyLayout(int width, MessageElement::Flags _flags)
// Painting
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();
QPixmap *pixmap = this->buffer_.get();
@ -132,7 +135,8 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
if (!pixmap) {
#ifdef Q_OS_MACOS
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());
#else
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
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
this->container_.paintAnimatedElements(painter, y);
// draw 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
@ -172,19 +178,23 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
// draw last read message line
if (isLastReadMessage) {
QColor color = isWindowFocused ? app->themes->tabs.selected.backgrounds.regular.color()
: app->themes->tabs.selected.backgrounds.unfocused.color();
QColor color =
isWindowFocused
? app->themes->tabs.selected.backgrounds.regular.color()
: app->themes->tabs.selected.backgrounds.unfocused.color();
QBrush brush(color,
static_cast<Qt::BrushStyle>(app->settings->lastMessagePattern.getValue()));
QBrush brush(color, static_cast<Qt::BrushStyle>(
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;
}
void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, Selection & /*selection*/)
void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
Selection & /*selection*/)
{
auto app = getApp();
@ -212,8 +222,8 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, Selectio
#ifdef FOURTF
// debug
painter.setPen(QColor(255, 0, 0));
painter.drawRect(buffer->rect().x(), buffer->rect().y(), buffer->rect().width() - 1,
buffer->rect().height() - 1);
painter.drawRect(buffer->rect().x(), buffer->rect().y(),
buffer->rect().width() - 1, buffer->rect().height() - 1);
QTextOption option;
option.setAlignment(Qt::AlignRight | Qt::AlignTop);

View file

@ -40,8 +40,9 @@ public:
bool layout(int width, float scale_, MessageElement::Flags flags);
// Painting
void paint(QPainter &painter, int width, int y, int messageIndex, Selection &selection,
bool isLastReadMessage, bool isWindowFocused);
void paint(QPainter &painter, int width, int y, int messageIndex,
Selection &selection, bool isLastReadMessage,
bool isWindowFocused);
void invalidateBuffer();
void deleteBuffer();
void deleteCache();

View file

@ -9,7 +9,8 @@
#include <QPainter>
#define COMPACT_EMOTES_OFFSET 6
#define MAX_UNCOLLAPSED_LINES (getApp()->settings->collpseMessagesMinLines.getValue())
#define MAX_UNCOLLAPSED_LINES \
(getApp()->settings->collpseMessagesMinLines.getValue())
namespace chatterino {
@ -29,13 +30,15 @@ float MessageLayoutContainer::getScale() const
}
// methods
void MessageLayoutContainer::begin(int width, float scale, Message::MessageFlags flags)
void MessageLayoutContainer::begin(int width, float scale,
Message::MessageFlags flags)
{
this->clear();
this->width_ = width;
this->scale_ = scale;
this->flags_ = flags;
auto mediumFontMetrics = getApp()->fonts->getFontMetrics(FontStyle::ChatMedium, scale);
auto mediumFontMetrics =
getApp()->fonts->getFontMetrics(FontStyle::ChatMedium, scale);
this->textLineHeight_ = mediumFontMetrics.height();
this->spaceWidth_ = mediumFontMetrics.width(' ');
this->dotdotdotWidth_ = mediumFontMetrics.width("...");
@ -66,7 +69,8 @@ void MessageLayoutContainer::addElement(MessageLayoutElement *element)
this->_addElement(element);
}
void MessageLayoutContainer::addElementNoLineBreak(MessageLayoutElement *element)
void MessageLayoutContainer::addElementNoLineBreak(
MessageLayoutElement *element)
{
this->_addElement(element);
}
@ -76,7 +80,8 @@ bool MessageLayoutContainer::canAddElements()
return this->canAddMessages_;
}
void MessageLayoutContainer::_addElement(MessageLayoutElement *element, bool forceAdd)
void MessageLayoutContainer::_addElement(MessageLayoutElement *element,
bool forceAdd)
{
if (!this->canAddElements() && !forceAdd) {
delete element;
@ -91,8 +96,9 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element, bool for
int newLineHeight = element->getRect().height();
// compact emote offset
bool isCompactEmote = !(this->flags_ & Message::DisableCompactEmotes) &&
element->getCreator().getFlags() & MessageElement::EmoteImages;
bool isCompactEmote =
!(this->flags_ & Message::DisableCompactEmotes) &&
element->getCreator().getFlags() & MessageElement::EmoteImages;
if (isCompactEmote) {
newLineHeight -= COMPACT_EMOTES_OFFSET * this->scale_;
@ -102,7 +108,8 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element, bool for
this->lineHeight_ = std::max(this->lineHeight_, newLineHeight);
// 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
this->elements_.push_back(std::unique_ptr<MessageLayoutElement>(element));
@ -120,35 +127,42 @@ void MessageLayoutContainer::breakLine()
int xOffset = 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++) {
MessageLayoutElement *element = this->elements_.at(i).get();
bool isCompactEmote = !(this->flags_ & Message::DisableCompactEmotes) &&
element->getCreator().getFlags() & MessageElement::EmoteImages;
bool isCompactEmote =
!(this->flags_ & Message::DisableCompactEmotes) &&
element->getCreator().getFlags() & MessageElement::EmoteImages;
int yExtra = 0;
if (isCompactEmote) {
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_) {
yExtra -= (this->textLineHeight_ - element->getRect().height()) / 2;
}
element->setPosition(QPoint(element->getRect().x() + xOffset + this->margin.left,
element->getRect().y() + this->lineHeight_ + yExtra));
element->setPosition(
QPoint(element->getRect().x() + xOffset + this->margin.left,
element->getRect().y() + this->lineHeight_ + yExtra));
}
if (this->lines_.size() != 0) {
this->lines_.back().endIndex = this->lineStart_;
this->lines_.back().endCharIndex = this->charIndex_;
}
this->lines_.push_back({(int)lineStart_, 0, this->charIndex_, 0,
QRect(-100000, this->currentY_, 200000, lineHeight_)});
this->lines_.push_back(
{(int)lineStart_, 0, this->charIndex_, 0,
QRect(-100000, this->currentY_, 200000, lineHeight_)});
for (int i = this->lineStart_; i < this->elements_.size(); i++) {
this->charIndex_ += this->elements_[i]->getSelectionIndexCount();
@ -178,17 +192,20 @@ bool MessageLayoutContainer::fitsInLine(int _width)
{
return this->currentX_ + _width <=
(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()
{
if (!this->canAddElements()) {
static TextElement dotdotdot("...", MessageElement::Collapsed, MessageColor::Link);
static TextElement dotdotdot("...", MessageElement::Collapsed,
MessageColor::Link);
static QString dotdotdotText("...");
auto *element = new TextLayoutElement(
dotdotdot, dotdotdotText, QSize(this->dotdotdotWidth_, this->textLineHeight_),
dotdotdot, dotdotdotText,
QSize(this->dotdotdotWidth_, this->textLineHeight_),
QColor("#00D80A"), FontStyle::ChatMediumBold, this->scale_);
// getApp()->themes->messages.textColors.system
@ -235,7 +252,8 @@ MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point)
// painting
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
painter.setPen(QColor(0, 255, 0));
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);
}
}
@ -273,7 +293,8 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
rect.setTop(std::max(0, rect.top()) + yOffset);
rect.setBottom(std::min(this->height_, rect.bottom()) + yOffset);
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);
}
@ -302,16 +323,19 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
int c = this->elements_[i]->getSelectionIndexCount();
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
if (selection.selectionMax.messageIndex == messageIndex &&
line.endCharIndex > /*=*/selection.selectionMax.charIndex) //
line.endCharIndex >
/*=*/selection.selectionMax.charIndex) //
{
returnAfter = true;
index = line.startCharIndex;
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) {
r = this->elements_[i]->getXFromIndex(
@ -330,9 +354,15 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
QRect rect = line.rect;
rect.setTop(std::max(0, rect.top()) + yOffset);
rect.setBottom(std::min(this->height_, rect.bottom()) + yOffset);
rect.setLeft(this->elements_[line.startIndex]->getRect().left());
rect.setRight(this->elements_[line.endIndex - 1]->getRect().right());
rect.setBottom(
std::min(this->height_, rect.bottom()) +
yOffset);
rect.setLeft(this->elements_[line.startIndex]
->getRect()
.left());
rect.setRight(this->elements_[line.endIndex - 1]
->getRect()
.right());
painter.fillRect(rect, selectionColor);
}
@ -378,7 +408,8 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
rect.setTop(std::max(0, rect.top()) + yOffset);
rect.setBottom(std::min(this->height_, rect.bottom()) + yOffset);
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);
continue;
@ -390,7 +421,8 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
int c = this->elements_[i]->getSelectionIndexCount();
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;
}
@ -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()) {
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;

View file

@ -66,7 +66,8 @@ struct MessageLayoutContainer {
// painting
void paintElements(QPainter &painter);
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
int getSelectionIndex(QPoint point);

View file

@ -14,7 +14,8 @@ const QRect &MessageLayoutElement::getRect() const
return this->rect_;
}
MessageLayoutElement::MessageLayoutElement(MessageElement &creator, const QSize &size)
MessageLayoutElement::MessageLayoutElement(MessageElement &creator,
const QSize &size)
: creator_(creator)
{
this->rect_.setSize(size);
@ -63,14 +64,16 @@ const Link &MessageLayoutElement::getLink() const
// IMAGE
//
ImageLayoutElement::ImageLayoutElement(MessageElement &creator, ImagePtr image, const QSize &size)
ImageLayoutElement::ImageLayoutElement(MessageElement &creator, ImagePtr image,
const QSize &size)
: MessageLayoutElement(creator, size)
, image_(image)
{
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 += "not implemented";
@ -134,8 +137,9 @@ int ImageLayoutElement::getXFromIndex(int index)
// TEXT
//
TextLayoutElement::TextLayoutElement(MessageElement &_creator, QString &_text, const QSize &_size,
QColor _color, FontStyle _style, float _scale)
TextLayoutElement::TextLayoutElement(MessageElement &_creator, QString &_text,
const QSize &_size, QColor _color,
FontStyle _style, float _scale)
: MessageLayoutElement(_creator, _size)
, text(_text)
, 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);
@ -166,8 +171,9 @@ void TextLayoutElement::paint(QPainter &painter)
painter.setFont(app->fonts->getFont(this->style, this->scale));
painter.drawText(QRectF(this->getRect().x(), this->getRect().y(), 10000, 10000), this->text,
QTextOption(Qt::AlignLeft | Qt::AlignTop));
painter.drawText(
QRectF(this->getRect().x(), this->getRect().y(), 10000, 10000),
this->text, QTextOption(Qt::AlignLeft | Qt::AlignTop));
}
void TextLayoutElement::paintAnimated(QPainter &, int)
@ -219,8 +225,10 @@ int TextLayoutElement::getXFromIndex(int index)
}
// TEXT ICON
TextIconLayoutElement::TextIconLayoutElement(MessageElement &creator, const QString &_line1,
const QString &_line2, float _scale, const QSize &size)
TextIconLayoutElement::TextIconLayoutElement(MessageElement &creator,
const QString &_line1,
const QString &_line2,
float _scale, const QSize &size)
: MessageLayoutElement(creator, size)
, scale(_scale)
, 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);
} else {
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);
painter.drawText(
QPoint(this->getRect().x(), this->getRect().y() + this->getRect().height()),
this->line2);
painter.drawText(QPoint(this->getRect().x(),
this->getRect().y() + this->getRect().height()),
this->line2);
}
}

View file

@ -31,7 +31,8 @@ public:
MessageLayoutElement *setTrailingSpace(bool value);
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 void paint(QPainter &painter) = 0;
virtual void paintAnimated(QPainter &painter, int yOffset) = 0;
@ -52,10 +53,12 @@ private:
class ImageLayoutElement : public MessageLayoutElement
{
public:
ImageLayoutElement(MessageElement &creator, ImagePtr image, const QSize &size);
ImageLayoutElement(MessageElement &creator, ImagePtr image,
const QSize &size);
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;
void paint(QPainter &painter) override;
void paintAnimated(QPainter &painter, int yOffset) override;
@ -70,11 +73,13 @@ private:
class TextLayoutElement : public MessageLayoutElement
{
public:
TextLayoutElement(MessageElement &creator_, QString &text, const QSize &size, QColor color,
FontStyle style, float scale);
TextLayoutElement(MessageElement &creator_, QString &text,
const QSize &size, QColor color, FontStyle style,
float scale);
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;
void paint(QPainter &painter) override;
void paintAnimated(QPainter &painter, int yOffset) override;
@ -93,11 +98,12 @@ private:
class TextIconLayoutElement : public MessageLayoutElement
{
public:
TextIconLayoutElement(MessageElement &creator_, const QString &line1, const QString &line2,
float scale, const QSize &size);
TextIconLayoutElement(MessageElement &creator_, const QString &line1,
const QString &line2, float scale, const QSize &size);
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;
void paint(QPainter &painter) override;
void paintAnimated(QPainter &painter, int yOffset) override;

View file

@ -13,11 +13,13 @@ namespace chatterino {
namespace {
Url getEmoteLink(QString urlTemplate, const EmoteId &id, const QString &emoteScale)
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
{
urlTemplate.detach();
return {urlTemplate.replace("{{id}}", id.string).replace("{{image}}", emoteScale)};
return {urlTemplate.replace("{{id}}", id.string)
.replace("{{image}}", emoteScale)};
}
} // namespace
@ -76,23 +78,26 @@ void BttvEmotes::loadGlobalEmotes()
request.execute();
}
std::pair<Outcome, EmoteMap> BttvEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot,
const EmoteMap &currentEmotes)
std::pair<Outcome, EmoteMap> BttvEmotes::parseGlobalEmotes(
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes)
{
auto emotes = EmoteMap();
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) {
auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
auto name = EmoteName{jsonEmote.toObject().value("code").toString()};
auto emote = Emote({name,
ImageSet{Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
Tooltip{name.string + "<br />Global Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}});
auto emote = Emote(
{name,
ImageSet{
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
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);
if (it != currentEmotes.end() && *it->second == emote) {

View file

@ -10,7 +10,8 @@ namespace chatterino {
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:
// BttvEmotes();
@ -22,8 +23,8 @@ public:
void loadGlobalEmotes();
private:
std::pair<Outcome, EmoteMap> parseGlobalEmotes(const QJsonObject &jsonRoot,
const EmoteMap &currentEmotes);
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes);
UniqueAccess<EmoteMap> globalEmotes_;
// UniqueAccess<WeakEmoteIdMap> channelEmoteCache_;

View file

@ -11,12 +11,16 @@
namespace chatterino {
static Url getEmoteLink(QString urlTemplate, const EmoteId &id, const QString &emoteScale);
static std::pair<Outcome, EmoteMap> bttvParseChannelEmotes(const QJsonObject &jsonRoot);
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
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.setTimeout(3000);
@ -31,14 +35,17 @@ void loadBttvChannelEmotes(const QString &channelName, std::function<void(EmoteM
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 emotes = EmoteMap();
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) {
auto jsonEmote = jsonEmote_.toObject();
@ -47,30 +54,35 @@ static std::pair<Outcome, EmoteMap> bttvParseChannelEmotes(const QJsonObject &js
auto name = EmoteName{jsonEmote.value("code").toString()};
// emoteObject.value("imageType").toString();
auto emote = Emote({name,
ImageSet{Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
Tooltip{name.string + "<br />Channel Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}});
auto emote = Emote(
{name,
ImageSet{
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
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();
if (shared && *shared == emote) {
// reuse old shared_ptr if nothing changed
emotes[name] = shared;
} 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)};
}
static Url getEmoteLink(QString urlTemplate, const EmoteId &id, const QString &emoteScale)
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
{
urlTemplate.detach();
return {urlTemplate.replace("{{id}}", id.string).replace("{{image}}", emoteScale)};
return {urlTemplate.replace("{{id}}", id.string)
.replace("{{image}}", emoteScale)};
}
} // namespace chatterino

View file

@ -7,8 +7,10 @@ class QString;
namespace chatterino {
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

View file

@ -32,11 +32,13 @@ void ChatterinoBadges::loadChatterinoBadges()
for (auto jsonBadge_ : jsonRoot.value("badges").toArray()) {
auto jsonBadge = jsonBadge_.toObject();
auto emote = Emote{EmoteName{}, ImageSet{Url{jsonBadge.value("image").toString()}},
Tooltip{jsonBadge.value("tooltip").toString()}, Url{}};
auto emote = Emote{
EmoteName{}, ImageSet{Url{jsonBadge.value("image").toString()}},
Tooltip{jsonBadge.value("tooltip").toString()}, Url{}};
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 {
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())
{
static uint unicodeBytes[4];
@ -80,7 +81,8 @@ void parseEmoji(const std::shared_ptr<EmojiData> &emojiData, const rapidjson::Va
int numUnicodeBytes = 0;
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);
@ -116,8 +118,8 @@ void Emojis::loadEmojis()
rapidjson::ParseResult result = root.Parse(data.toUtf8(), data.length());
if (result.Code() != rapidjson::kParseErrorNone) {
Log("JSON parse error: {} ({})", rapidjson::GetParseError_En(result.Code()),
result.Offset());
Log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset());
return;
}
@ -135,7 +137,8 @@ void Emojis::loadEmojis()
this->emojis.insert(emojiData->unifiedCode, emojiData);
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();
const auto &variation = skinVariation.value;
@ -143,20 +146,23 @@ void Emojis::loadEmojis()
auto toneNameIt = toneNames.find(tone);
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;
}
parseEmoji(variationEmojiData, variation,
emojiData->shortCodes[0] + "_" + toneNameIt->second);
this->emojiShortCodeToEmoji_.insert(variationEmojiData->shortCodes[0],
variationEmojiData);
this->emojiShortCodeToEmoji_.insert(
variationEmojiData->shortCodes[0], variationEmojiData);
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()
{
for (auto &p : this->emojiFirstByte_) {
std::stable_sort(p.begin(), p.end(), [](const auto &lhs, const auto &rhs) {
return lhs->value.length() > rhs->value.length();
});
std::stable_sort(p.begin(), p.end(),
[](const auto &lhs, const auto &rhs) {
return lhs->value.length() > rhs->value.length();
});
}
auto &p = this->shortCodes;
std::stable_sort(p.begin(), p.end(),
[](const auto &lhs, const auto &rhs) { return lhs < rhs; });
std::stable_sort(p.begin(), p.end(), [](const auto &lhs, const auto &rhs) {
return lhs < rhs;
});
}
void Emojis::loadEmojiSet()
@ -212,7 +220,8 @@ void Emojis::loadEmojiSet()
app->settings->emojiSet.connect([=](const auto &emojiSet, auto) {
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;
// clang-format off
static std::map<QString, QString> emojiSets = {
@ -259,20 +268,22 @@ void Emojis::loadEmojiSet()
}
}
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);
if (it != emojiSets.end()) {
urlPrefix = it->second;
}
QString url = urlPrefix + code + ".png";
emoji->emote = std::make_shared<Emote>(
Emote{EmoteName{emoji->value}, ImageSet{Image::fromUrl({url}, 0.35)},
Tooltip{":" + emoji->shortCodes[0] + ":<br/>Emoji"}, Url{}});
emoji->emote = std::make_shared<Emote>(Emote{
EmoteName{emoji->value}, ImageSet{Image::fromUrl({url}, 0.35)},
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>>();
int lastParsedEmojiEndIndex = 0;
@ -330,11 +341,13 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(const QString &text
int currentParsedEmojiFirstIndex = i;
int currentParsedEmojiEndIndex = i + (matchedEmojiLength);
int charactersFromLastParsedEmoji = currentParsedEmojiFirstIndex - lastParsedEmojiEndIndex;
int charactersFromLastParsedEmoji =
currentParsedEmojiFirstIndex - lastParsedEmojiEndIndex;
if (charactersFromLastParsedEmoji > 0) {
// 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
@ -365,7 +378,8 @@ QString Emojis::replaceShortCodes(const QString &text)
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);
@ -375,7 +389,8 @@ QString Emojis::replaceShortCodes(const QString &text)
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();
}

View file

@ -15,7 +15,8 @@
namespace chatterino {
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;
// i.e. 204e-50a2
@ -57,7 +58,8 @@ private:
// shortCodeToEmoji maps strings like "sunglasses" to its emoji
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_;
};

View file

@ -20,8 +20,8 @@ Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
return {"https:" + emote.toString()};
}
void fillInEmoteData(const QJsonObject &urls, const EmoteName &name, const QString &tooltip,
Emote &emoteData)
void fillInEmoteData(const QJsonObject &urls, const EmoteName &name,
const QString &tooltip, Emote &emoteData)
{
auto url1x = getEmoteLink(urls, "1");
auto url2x = getEmoteLink(urls, "2");
@ -30,7 +30,8 @@ void fillInEmoteData(const QJsonObject &urls, const EmoteName &name, const QStri
//, code, tooltip
emoteData.name = name;
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};
}
} // namespace
@ -67,8 +68,9 @@ void FfzEmotes::loadGlobalEmotes()
NetworkRequest request(url);
request.setCaller(QThread::currentThread());
request.setTimeout(30000);
request.onSuccess(
[this](auto result) -> Outcome { return this->parseGlobalEmotes(result.parseJson()); });
request.onSuccess([this](auto result) -> Outcome {
return this->parseGlobalEmotes(result.parseJson());
});
request.execute();
}
@ -90,10 +92,12 @@ Outcome FfzEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot)
auto urls = jsonEmote.value("urls").toObject();
auto emote = Emote();
fillInEmoteData(urls, name, name.string + "<br/>Global FFZ Emote", emote);
emote.homePage = Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
.arg(id.string)
.arg(name.string)};
fillInEmoteData(urls, name, name.string + "<br/>Global FFZ Emote",
emote);
emote.homePage =
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
.arg(id.string)
.arg(name.string)};
replacement.add(name, emote);
}
@ -105,7 +109,8 @@ Outcome FfzEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot)
void FfzEmotes::loadChannelEmotes(const QString &channelName,
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);
@ -145,10 +150,11 @@ Outcome parseChannelEmotes(const QJsonObject &jsonRoot)
// 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;
// fillInEmoteData(urls, code, code + "<br/>Channel FFZ Emote", emoteData);
// emoteData.pageLink =
// fillInEmoteData(urls, code, code + "<br/>Channel FFZ Emote",
// emoteData); emoteData.pageLink =
// QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
// return emoteData;

View file

@ -10,8 +10,10 @@ namespace chatterino {
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 *channelEmoteApiUrl = "https://api.betterttv.net/2/channels/";
static constexpr const char *globalEmoteApiUrl =
"https://api.frankerfacez.com/v1/set/global";
static constexpr const char *channelEmoteApiUrl =
"https://api.betterttv.net/2/channels/";
public:
// FfzEmotes();
@ -23,7 +25,8 @@ public:
boost::optional<EmotePtr> getEmote(const EmoteId &id);
void loadGlobalEmotes();
void loadChannelEmotes(const QString &channelName, std::function<void(EmoteMap &&)> callback);
void loadChannelEmotes(const QString &channelName,
std::function<void(EmoteMap &&)> callback);
protected:
Outcome parseGlobalEmotes(const QJsonObject &jsonRoot);

View file

@ -12,27 +12,35 @@ AbstractIrcServer::AbstractIrcServer()
{
// Initialize the connections
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,
[this](auto msg) { this->writeConnectionMessageReceived(msg); });
QObject::connect(
this->writeConnection_.get(), &Communi::IrcConnection::messageReceived,
[this](auto msg) { this->writeConnectionMessageReceived(msg); });
// Listen to read connection message signals
this->readConnection_.reset(new IrcConnection);
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); });
QObject::connect(this->readConnection_.get(), &Communi::IrcConnection::privateMessageReceived,
QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::privateMessageReceived,
[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(); });
QObject::connect(this->readConnection_.get(), &Communi::IrcConnection::disconnected,
QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::disconnected,
[this] { this->onDisconnected(); });
// listen to reconnect request
this->readConnection_->reconnectRequested.connect([this] { this->connect(); });
// this->writeConnection->reconnectRequested.connect([this] { this->connect(); });
this->readConnection_->reconnectRequested.connect(
[this] { this->connect(); });
// this->writeConnection->reconnectRequested.connect([this] {
// this->connect(); });
}
void AbstractIrcServer::connect()
@ -75,7 +83,8 @@ void AbstractIrcServer::disconnect()
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);
}
@ -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);
@ -119,7 +130,8 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(const QString &dirty
chan->destroyed.connect([this, clojuresInCppAreShit] {
// fourtf: issues when the server itself is destroyed
Log("[AbstractIrcServer::addChannel] {} was destroyed", clojuresInCppAreShit);
Log("[AbstractIrcServer::addChannel] {} was destroyed",
clojuresInCppAreShit);
this->channels.remove(clojuresInCppAreShit);
if (this->readConnection_) {
@ -147,7 +159,8 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(const QString &dirty
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);
@ -187,9 +200,9 @@ void AbstractIrcServer::onConnected()
LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot();
bool replaceMessage =
snapshot.getLength() > 0 &&
snapshot[snapshot.getLength() - 1]->flags & Message::DisconnectedMessage;
bool replaceMessage = snapshot.getLength() > 0 &&
snapshot[snapshot.getLength() - 1]->flags &
Message::DisconnectedMessage;
if (replaceMessage) {
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;
}
@ -229,16 +243,19 @@ QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName)
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") {
this->privateMessageReceived(static_cast<Communi::IrcPrivateMessage *>(fakeMessage));
this->privateMessageReceived(
static_cast<Communi::IrcPrivateMessage *>(fakeMessage));
} else {
this->messageReceived(fakeMessage);
}
}
void AbstractIrcServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
void AbstractIrcServer::privateMessageReceived(
Communi::IrcPrivateMessage *message)
{
}

View file

@ -30,7 +30,8 @@ public:
// signals
pajlada::Signals::NoArgSignal connected;
pajlada::Signals::NoArgSignal disconnected;
// pajlada::Signals::Signal<Communi::IrcPrivateMessage *> onPrivateMessage;
// pajlada::Signals::Signal<Communi::IrcPrivateMessage *>
// onPrivateMessage;
void addFakeMessage(const QString &data);
@ -40,8 +41,10 @@ public:
protected:
AbstractIrcServer();
virtual void initializeConnection(IrcConnection *connection, bool isRead, bool isWrite) = 0;
virtual std::shared_ptr<Channel> createChannel(const QString &channelName) = 0;
virtual void initializeConnection(IrcConnection *connection, bool isRead,
bool isWrite) = 0;
virtual std::shared_ptr<Channel> createChannel(
const QString &channelName) = 0;
virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
virtual void messageReceived(Communi::IrcMessage *message);
@ -50,7 +53,8 @@ protected:
virtual void onConnected();
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 QString cleanChannelName(const QString &dirtyChannelName);

View file

@ -2,7 +2,8 @@
// namespace chatterino {
//
// IrcAccount::IrcAccount(const QString &_userName, const QString &_nickName, const QString
// IrcAccount::IrcAccount(const QString &_userName, const QString &_nickName,
// const QString
// &_realName,
// const QString &_password)
// : userName(_userName)

View file

@ -7,7 +7,8 @@
// class IrcAccount
//{
// 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 &getUserName() const;

View file

@ -27,13 +27,14 @@ IrcConnection::IrcConnection(QObject *parent)
}
});
QObject::connect(this, &Communi::IrcConnection::messageReceived, [this](Communi::IrcMessage *) {
this->recentlyReceivedMessage_ = true;
QObject::connect(this, &Communi::IrcConnection::messageReceived,
[this](Communi::IrcMessage *) {
this->recentlyReceivedMessage_ = true;
if (this->reconnectTimer_.isActive()) {
this->reconnectTimer_.stop();
}
});
if (this->reconnectTimer_.isActive()) {
this->reconnectTimer_.stop();
}
});
}
} // namespace chatterino

View file

@ -14,7 +14,8 @@ namespace chatterino {
// std::shared_ptr<IrcAccount> getAccount() const;
// 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 messageReceived(Communi::IrcMessage *message);

View file

@ -25,15 +25,17 @@ IrcMessageHandler &IrcMessageHandler::getInstance()
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,
message->isAction());
this->addMessage(message, message->target(), message->content(), server,
false, message->isAction());
}
void IrcMessageHandler::addMessage(Communi::IrcMessage *_message, const QString &target,
const QString &content, TwitchServer &server, bool isSub,
bool isAction)
void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
const QString &target,
const QString &content, TwitchServer &server,
bool isSub, bool isAction)
{
QString channelName;
if (!trimChannelName(target, channelName)) {
@ -140,14 +142,17 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
auto chan = app->twitch.server->getChannelOrEmpty(chanName);
if (chan->isEmpty()) {
Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not found", chanName);
Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
"found",
chanName);
return;
}
// check if the chat has been cleared by a moderator
if (message->parameters().length() == 1) {
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;
}
@ -165,7 +170,8 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
reason = v.toString();
}
auto timeoutMsg = Message::createTimeoutMessage(username, durationInSeconds, reason, false);
auto timeoutMsg = Message::createTimeoutMessage(username, durationInSeconds,
reason, false);
chan->addOrReplaceTimeout(timeoutMsg);
// refresh all
@ -206,7 +212,8 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
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()) {
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();
@ -244,7 +252,8 @@ void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message, Tw
}
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()) {
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");
if (it != tags.end()) {
auto newMessage = Message::createSystemMessage(parseTagString(it.value().toString()));
auto newMessage =
Message::createSystemMessage(parseTagString(it.value().toString()));
newMessage->flags |= Message::Subscription;
@ -279,7 +289,8 @@ void IrcMessageHandler::handleModeMessage(Communi::IrcMessage *message)
{
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()) {
return;
@ -299,10 +310,12 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
QString channelName;
if (!trimChannelName(message->target(), channelName)) {
// Notice wasn't targeted at a single channel, send to all twitch channels
app->twitch.server->forEachChannelAndSpecialChannels([msg](const auto &c) {
c->addMessage(msg); //
});
// Notice wasn't targeted at a single channel, send to all twitch
// channels
app->twitch.server->forEachChannelAndSpecialChannels(
[msg](const auto &c) {
c->addMessage(msg); //
});
return;
}
@ -310,7 +323,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
auto channel = app->twitch.server->getChannelOrEmpty(channelName);
if (channel->isEmpty()) {
Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel manager ",
Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel "
"manager ",
channelName);
return;
}
@ -318,7 +332,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
channel->addMessage(msg);
}
void IrcMessageHandler::handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message)
void IrcMessageHandler::handleWriteConnectionNoticeMessage(
Communi::IrcNoticeMessage *message)
{
static std::unordered_set<std::string> readConnectionOnlyIDs{
"host_on",
@ -333,8 +348,9 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(Communi::IrcNoticeMes
"r9k_on",
"r9k_off",
// Display for user who times someone out. This implies you're a moderator, at which point
// you will be connected to PubSub and receive a better message from there
// Display for user who times someone out. This implies you're a
// moderator, at which point you will be connected to PubSub and receive
// a better message from there
"timeout_success",
"ban_success",
};
@ -347,7 +363,8 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(Communi::IrcNoticeMes
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);
@ -356,9 +373,11 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(Communi::IrcNoticeMes
void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
{
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());
}
}
@ -366,9 +385,11 @@ void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message)
{
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());
}
}

View file

@ -13,13 +13,15 @@ class IrcMessageHandler
public:
static IrcMessageHandler &getInstance();
void handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchServer &server);
void handlePrivMessage(Communi::IrcPrivateMessage *message,
TwitchServer &server);
void handleRoomStateMessage(Communi::IrcMessage *message);
void handleClearChatMessage(Communi::IrcMessage *message);
void handleUserStateMessage(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 handleNoticeMessage(Communi::IrcNoticeMessage *message);
void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message);
@ -28,8 +30,9 @@ public:
void handlePartMessage(Communi::IrcMessage *message);
private:
void addMessage(Communi::IrcMessage *message, const QString &target, const QString &content,
TwitchServer &server, bool isResub, bool isAction);
void addMessage(Communi::IrcMessage *message, const QString &target,
const QString &content, TwitchServer &server, bool isResub,
bool isAction);
};
} // namespace chatterino

View file

@ -25,7 +25,8 @@ PartialTwitchUser PartialTwitchUser::byId(const QString &id)
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());
@ -33,7 +34,8 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback, cons
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.makeAuthorizedV5(getDefaultClientID());
@ -56,7 +58,8 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback, cons
auto firstUser = users[0].toObject();
auto id = firstUser.value("_id");
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");
return Failure;
}

View file

@ -19,7 +19,8 @@ public:
static PartialTwitchUser byName(const QString &username);
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

View file

@ -21,7 +21,8 @@ struct PubSubAction {
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 {
using PubSubAction::PubSubAction;

View file

@ -24,7 +24,8 @@ static std::map<QString, std::string> sentMessages;
namespace detail {
PubSubClient::PubSubClient(WebsocketClient &websocketClient, WebsocketHandle handle)
PubSubClient::PubSubClient(WebsocketClient &websocketClient,
WebsocketHandle handle)
: websocketClient_(websocketClient)
, handle_(handle)
{
@ -58,7 +59,8 @@ bool PubSubClient::listen(rapidjson::Document &message)
this->numListens_ += numRequestedListens;
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();
@ -135,34 +137,38 @@ void PubSubClient::ping()
auto self = this->shared_from_this();
runAfter(this->websocketClient_.get_io_service(), std::chrono::seconds(15), [self](auto timer) {
if (!self->started_) {
return;
}
runAfter(this->websocketClient_.get_io_service(), std::chrono::seconds(15),
[self](auto timer) {
if (!self->started_) {
return;
}
if (self->awaitingPong_) {
Log("No pong respnose, disconnect!");
// TODO(pajlada): Label this connection as "disconnect me"
}
});
if (self->awaitingPong_) {
Log("No pong respnose, disconnect!");
// TODO(pajlada): Label this connection as "disconnect me"
}
});
runAfter(this->websocketClient_.get_io_service(), std::chrono::minutes(5), [self](auto timer) {
if (!self->started_) {
return;
}
runAfter(this->websocketClient_.get_io_service(), std::chrono::minutes(5),
[self](auto timer) {
if (!self->started_) {
return;
}
self->ping(); //
});
self->ping(); //
});
}
bool PubSubClient::send(const char *payload)
{
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) {
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;
}
@ -176,13 +182,15 @@ PubSub::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);
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);
action.mode = ModeChangedAction::Mode::Slow;
@ -191,7 +199,8 @@ PubSub::PubSub()
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);
action.mode = ModeChangedAction::Mode::Slow;
@ -228,7 +237,8 @@ PubSub::PubSub()
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);
action.mode = ModeChangedAction::Mode::R9K;
@ -237,7 +247,8 @@ PubSub::PubSub()
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);
action.mode = ModeChangedAction::Mode::R9K;
@ -246,17 +257,18 @@ PubSub::PubSub()
this->signals_.moderation.modeChanged.invoke(action);
};
this->moderationActionHandlers["subscribersoff"] = [this](const auto &data,
const auto &roomID) {
ModeChangedAction action(data, roomID);
this->moderationActionHandlers["subscribersoff"] =
[this](const auto &data, const auto &roomID) {
ModeChangedAction action(data, roomID);
action.mode = ModeChangedAction::Mode::SubscribersOnly;
action.state = ModeChangedAction::State::Off;
action.mode = ModeChangedAction::Mode::SubscribersOnly;
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);
action.mode = ModeChangedAction::Mode::SubscribersOnly;
@ -265,16 +277,18 @@ PubSub::PubSub()
this->signals_.moderation.modeChanged.invoke(action);
};
this->moderationActionHandlers["emoteonlyoff"] = [this](const auto &data, const auto &roomID) {
ModeChangedAction action(data, roomID);
this->moderationActionHandlers["emoteonlyoff"] =
[this](const auto &data, const auto &roomID) {
ModeChangedAction action(data, roomID);
action.mode = ModeChangedAction::Mode::EmoteOnly;
action.state = ModeChangedAction::State::Off;
action.mode = ModeChangedAction::Mode::EmoteOnly;
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);
action.mode = ModeChangedAction::Mode::EmoteOnly;
@ -283,7 +297,8 @@ PubSub::PubSub()
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);
getTargetUser(data, action.target);
@ -307,7 +322,8 @@ PubSub::PubSub()
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);
getTargetUser(data, action.target);
@ -331,7 +347,8 @@ PubSub::PubSub()
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);
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);
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);
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);
getCreatedByUser(data, action.source);
@ -447,16 +467,21 @@ PubSub::PubSub()
};
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();
// 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_open_handler(bind(&PubSub::onConnectionOpen, this, ::_1));
this->websocketClient.set_close_handler(bind(&PubSub::onConnectionClose, this, ::_1));
this->websocketClient.set_message_handler(
bind(&PubSub::onMessage, this, ::_1, ::_2));
this->websocketClient.set_open_handler(
bind(&PubSub::onConnectionOpen, this, ::_1));
this->websocketClient.set_close_handler(
bind(&PubSub::onConnectionClose, this, ::_1));
// Add an initial client
this->addClient();
@ -477,7 +502,8 @@ void PubSub::addClient()
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)
@ -507,8 +533,8 @@ void PubSub::unlistenAllModerationActions()
}
}
void PubSub::listenToChannelModerationActions(const QString &channelID,
std::shared_ptr<TwitchAccount> account)
void PubSub::listenToChannelModerationActions(
const QString &channelID, std::shared_ptr<TwitchAccount> account)
{
assert(!channelID.isEmpty());
assert(account != nullptr);
@ -527,7 +553,8 @@ void PubSub::listenToChannelModerationActions(const QString &channelID,
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);
@ -542,7 +569,8 @@ void PubSub::listen(rapidjson::Document &&msg)
}
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)
@ -570,7 +598,8 @@ bool PubSub::isListeningToTopic(const std::string &topic)
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();
@ -585,7 +614,9 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr webs
}
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;
}
@ -615,8 +646,8 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr webs
} else if (type == "PONG") {
auto clientIt = this->clients.find(hdl);
// If this assert goes off, there's something wrong with the connection creation/preserving
// code KKona
// If this assert goes off, there's something wrong with the connection
// creation/preserving code KKona
assert(clientIt != this->clients.end());
auto &client = *clientIt;
@ -629,9 +660,11 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr webs
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();
this->clients.emplace(hdl, client);
@ -643,8 +676,8 @@ void PubSub::onConnectionClose(WebsocketHandle hdl)
{
auto clientIt = this->clients.find(hdl);
// If this assert goes off, there's something wrong with the connection creation/preserving
// code KKona
// If this assert goes off, there's something wrong with the connection
// creation/preserving code KKona
assert(clientIt != this->clients.end());
auto &client = clientIt->second;
@ -658,7 +691,8 @@ void PubSub::onConnectionClose(WebsocketHandle 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 {
ctx->set_options(boost::asio::ssl::context::default_workarounds |

View file

@ -22,7 +22,8 @@
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 WebsocketErrorCode = websocketpp::lib::error_code;
@ -71,11 +72,14 @@ private:
class PubSub
{
using WebsocketMessagePtr = websocketpp::config::asio_tls_client::message_type::ptr;
using WebsocketContextPtr = websocketpp::lib::shared_ptr<boost::asio::ssl::context>;
using WebsocketMessagePtr =
websocketpp::config::asio_tls_client::message_type::ptr;
using WebsocketContextPtr =
websocketpp::lib::shared_ptr<boost::asio::ssl::context>;
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;
std::unique_ptr<std::thread> mainThread;
@ -121,13 +125,14 @@ public:
void unlistenAllModerationActions();
void listenToChannelModerationActions(const QString &channelID,
std::shared_ptr<TwitchAccount> account);
void listenToChannelModerationActions(
const QString &channelID, std::shared_ptr<TwitchAccount> account);
std::vector<std::unique_ptr<rapidjson::Document>> requests;
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);
bool tryListen(rapidjson::Document &msg);
@ -142,7 +147,8 @@ private:
std::owner_less<WebsocketHandle>>
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;
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);
}
rapidjson::Document createListenMessage(const std::vector<std::string> &topicsVec,
std::shared_ptr<TwitchAccount> account)
rapidjson::Document createListenMessage(
const std::vector<std::string> &topicsVec,
std::shared_ptr<TwitchAccount> account)
{
rapidjson::Document msg(rapidjson::kObjectType);
auto &a = msg.GetAllocator();
@ -57,7 +58,8 @@ rapidjson::Document createListenMessage(const std::vector<std::string> &topicsVe
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);
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);
rapidjson::Document createListenMessage(const std::vector<std::string> &topicsVec,
std::shared_ptr<TwitchAccount> account);
rapidjson::Document createUnlistenMessage(const std::vector<std::string> &topicsVec);
rapidjson::Document createListenMessage(
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
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);
timer->expires_from_now(duration);
@ -42,7 +45,8 @@ void runAfter(boost::asio::io_service &ioService, Duration duration, Callback cb
// Use provided timer
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);

View file

@ -20,10 +20,12 @@ EmoteName cleanUpCode(const EmoteName &dirtyEmoteCode)
cleanCode.detach();
static QMap<QString, QString> emoteNameReplacements{
{"[oO](_|\\.)[oO]", "O_o"}, {"\\&gt\\;\\(", "&gt;("}, {"\\&lt\\;3", "&lt;3"},
{"\\:-?(o|O)", ":O"}, {"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"},
{"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("}, {"\\:-?\\)", ":)"},
{"\\:-?D", ":D"}, {"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"},
{"[oO](_|\\.)[oO]", "O_o"}, {"\\&gt\\;\\(", "&gt;("},
{"\\&lt\\;3", "&lt;3"}, {"\\:-?(o|O)", ":O"},
{"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"},
{"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("},
{"\\:-?\\)", ":)"}, {"\\:-?D", ":D"},
{"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"},
{"R-?\\)", "R)"}, {"B-?\\)", "B)"},
};
@ -105,7 +107,8 @@ bool TwitchAccount::isAnon() const
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);
req.setCaller(QThread::currentThread());
@ -140,7 +143,8 @@ void TwitchAccount::loadIgnores()
}
TwitchUser 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;
}
@ -154,28 +158,32 @@ void TwitchAccount::loadIgnores()
req.execute();
}
void TwitchAccount::ignore(const QString &targetName,
std::function<void(IgnoreResult, const QString &)> onFinished)
void TwitchAccount::ignore(
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); //
};
PartialTwitchUser::byName(targetName).getId(onIdFetched);
}
void TwitchAccount::ignoreByID(const QString &targetUserID, const QString &targetName,
std::function<void(IgnoreResult, const QString &)> onFinished)
void TwitchAccount::ignoreByID(
const QString &targetUserID, const QString &targetName,
std::function<void(IgnoreResult, const QString &)> onFinished)
{
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() + "/blocks/" +
targetUserID);
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() +
"/blocks/" + targetUserID);
NetworkRequest req(url, NetworkRequestType::Put);
req.setCaller(QThread::currentThread());
req.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken());
req.onError([=](int errorCode) {
onFinished(IgnoreResult_Failed, "An unknown error occured while trying to ignore user " +
targetName + " (" + QString::number(errorCode) + ")");
onFinished(IgnoreResult_Failed,
"An unknown error occured while trying to ignore user " +
targetName + " (" + QString::number(errorCode) + ")");
return true;
});
@ -183,21 +191,24 @@ void TwitchAccount::ignoreByID(const QString &targetUserID, const QString &targe
req.onSuccess([=](auto result) -> Outcome {
auto document = result.parseRapidJson();
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;
}
auto userIt = document.FindMember("user");
if (userIt == document.MemberEnd()) {
onFinished(IgnoreResult_Failed,
"Bad JSON data while ignoring user (missing user) " + targetName);
"Bad JSON data while ignoring user (missing user) " +
targetName);
return Failure;
}
TwitchUser ignoredUser;
if (!rj::getSafe(userIt->value, ignoredUser)) {
onFinished(IgnoreResult_Failed,
"Bad JSON data while ignoring user (invalid user) " + targetName);
"Bad JSON data while ignoring user (invalid user) " +
targetName);
return Failure;
}
{
@ -212,7 +223,8 @@ void TwitchAccount::ignoreByID(const QString &targetUserID, const QString &targe
return Failure;
}
}
onFinished(IgnoreResult_Success, "Successfully ignored user " + targetName);
onFinished(IgnoreResult_Success,
"Successfully ignored user " + targetName);
return Success;
});
@ -220,10 +232,12 @@ void TwitchAccount::ignoreByID(const QString &targetUserID, const QString &targe
req.execute();
}
void TwitchAccount::unignore(const QString &targetName,
std::function<void(UnignoreResult, const QString &message)> onFinished)
void TwitchAccount::unignore(
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); //
};
@ -234,8 +248,8 @@ void TwitchAccount::unignoreByID(
const QString &targetUserID, const QString &targetName,
std::function<void(UnignoreResult, const QString &message)> onFinished)
{
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() + "/blocks/" +
targetUserID);
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() +
"/blocks/" + targetUserID);
NetworkRequest req(url, NetworkRequestType::Delete);
req.setCaller(QThread::currentThread());
@ -243,8 +257,8 @@ void TwitchAccount::unignoreByID(
req.onError([=](int errorCode) {
onFinished(UnignoreResult_Failed,
"An unknown error occured while trying to unignore user " + targetName + " (" +
QString::number(errorCode) + ")");
"An unknown error occured while trying to unignore user " +
targetName + " (" + QString::number(errorCode) + ")");
return true;
});
@ -258,7 +272,8 @@ void TwitchAccount::unignoreByID(
this->ignores_.erase(ignoredUser);
}
onFinished(UnignoreResult_Success, "Successfully unignored user " + targetName);
onFinished(UnignoreResult_Success,
"Successfully unignored user " + targetName);
return Success;
});
@ -269,8 +284,8 @@ void TwitchAccount::unignoreByID(
void TwitchAccount::checkFollow(const QString targetUserID,
std::function<void(FollowResult)> onFinished)
{
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() + "/follows/channels/" +
targetUserID);
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() +
"/follows/channels/" + targetUserID);
NetworkRequest req(url);
req.setCaller(QThread::currentThread());
@ -295,7 +310,8 @@ void TwitchAccount::checkFollow(const QString targetUserID,
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() +
"/follows/channels/" + userID);
@ -315,7 +331,8 @@ void TwitchAccount::followUser(const QString userID, std::function<void()> succe
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() +
"/follows/channels/" + userID);
@ -361,7 +378,8 @@ void TwitchAccount::loadEmotes()
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);
req.setCaller(QThread::currentThread());
@ -387,7 +405,8 @@ void TwitchAccount::loadEmotes()
req.execute();
}
AccessGuard<const TwitchAccount::TwitchAccountEmoteData> TwitchAccount::accessEmotes() const
AccessGuard<const TwitchAccount::TwitchAccountEmoteData>
TwitchAccount::accessEmotes() const
{
return this->emotes_.accessConst();
}
@ -412,7 +431,8 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
this->loadEmoteSetData(emoteSet);
for (const rapidjson::Value &emoteJSON : emoteSetJSON.value.GetArray()) {
for (const rapidjson::Value &emoteJSON :
emoteSetJSON.value.GetArray()) {
if (!emoteJSON.IsObject()) {
Log("Emote value was invalid");
return;
@ -459,8 +479,9 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
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.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);
if (type == "sub") {
emoteSet->text = QString("Twitch Subscriber Emote (%1)").arg(channelName);
emoteSet->text =
QString("Twitch Subscriber Emote (%1)").arg(channelName);
} else {
emoteSet->text = QString("Twitch Account Emote (%1)").arg(channelName);
emoteSet->text =
QString("Twitch Account Emote (%1)").arg(channelName);
}
emoteSet->channelName = channelName;

View file

@ -57,8 +57,8 @@ public:
EmoteMap emotes;
};
TwitchAccount(const QString &username, const QString &oauthToken_, const QString &oauthClient_,
const QString &_userID);
TwitchAccount(const QString &username, const QString &oauthToken_,
const QString &oauthClient_, const QString &_userID);
virtual QString toString() const override;
@ -81,16 +81,22 @@ public:
void loadIgnores();
void ignore(const QString &targetName,
std::function<void(IgnoreResult, const QString &)> onFinished);
void ignoreByID(const QString &targetUserID, const QString &targetName,
std::function<void(IgnoreResult, const QString &)> onFinished);
void unignore(const QString &targetName,
std::function<void(UnignoreResult, const QString &)> onFinished);
void unignoreByID(const QString &targetUserID, const QString &targetName,
std::function<void(UnignoreResult, const QString &message)> onFinished);
void ignoreByID(
const QString &targetUserID, const QString &targetName,
std::function<void(IgnoreResult, const QString &)> onFinished);
void unignore(
const QString &targetName,
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 followUser(const QString userID, std::function<void()> successCallback);
void unfollowUser(const QString userID, std::function<void()> successCallback);
void checkFollow(const QString targetUserID,
std::function<void(FollowResult)> onFinished);
void followUser(const QString userID,
std::function<void()> successCallback);
void unfollowUser(const QString userID,
std::function<void()> successCallback);
std::set<TwitchUser> getIgnores() const;

View file

@ -72,16 +72,17 @@ void TwitchAccountManager::reloadUsers()
continue;
}
std::string username =
pajlada::Settings::Setting<std::string>::get("/accounts/" + uid + "/username");
std::string userID =
pajlada::Settings::Setting<std::string>::get("/accounts/" + uid + "/userID");
std::string clientID =
pajlada::Settings::Setting<std::string>::get("/accounts/" + uid + "/clientID");
std::string oauthToken =
pajlada::Settings::Setting<std::string>::get("/accounts/" + uid + "/oauthToken");
std::string username = pajlada::Settings::Setting<std::string>::get(
"/accounts/" + uid + "/username");
std::string userID = pajlada::Settings::Setting<std::string>::get(
"/accounts/" + uid + "/userID");
std::string clientID = pajlada::Settings::Setting<std::string>::get(
"/accounts/" + uid + "/clientID");
std::string oauthToken = pajlada::Settings::Setting<std::string>::get(
"/accounts/" + uid + "/oauthToken");
if (username.empty() || userID.empty() || clientID.empty() || oauthToken.empty()) {
if (username.empty() || userID.empty() || clientID.empty() ||
oauthToken.empty()) {
continue;
}
@ -96,9 +97,11 @@ void TwitchAccountManager::reloadUsers()
// Do nothing
} break;
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()) {
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();
}
} break;
@ -122,11 +125,13 @@ void TwitchAccountManager::load()
QString newUsername(QString::fromStdString(newValue));
auto user = this->findUserByUsername(newUsername);
if (user) {
Log("[AccountManager:currentUsernameChanged] User successfully updated to {}",
Log("[AccountManager:currentUsernameChanged] User successfully "
"updated to {}",
newUsername);
this->currentUser_ = user;
} else {
Log("[AccountManager:currentUsernameChanged] User successfully updated to anonymous");
Log("[AccountManager:currentUsernameChanged] User successfully "
"updated to anonymous");
this->currentUser_ = this->anonymousUser_;
}
@ -140,8 +145,8 @@ bool TwitchAccountManager::isLoggedIn() const
return false;
}
// Once `TwitchAccount` class has a way to check, we should also return false if the credentials
// are incorrect
// Once `TwitchAccount` class has a way to check, we should also return
// false if the credentials are incorrect
return !this->currentUser_->isAnon();
}
@ -151,11 +156,13 @@ bool TwitchAccountManager::removeUser(TwitchAccount *account)
std::string userID(account->getUserId().toStdString());
if (!userID.empty()) {
pajlada::Settings::SettingManager::removeSetting("/accounts/uid" + userID);
pajlada::Settings::SettingManager::removeSetting("/accounts/uid" +
userID);
}
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 = "";
}
@ -186,8 +193,9 @@ TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
}
}
auto newUser = std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
userData.clientID, userData.userID);
auto newUser =
std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
userData.clientID, userData.userID);
// std::lock_guard<std::mutex> lock(this->mutex);

View file

@ -11,7 +11,8 @@
//
// 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 {
@ -30,12 +31,14 @@ public:
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::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;
void reloadUsers();
@ -43,11 +46,13 @@ public:
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 userListUpdated;
SortedSignalVector<std::shared_ptr<TwitchAccount>, SharedPtrElementLess<TwitchAccount>>
SortedSignalVector<std::shared_ptr<TwitchAccount>,
SharedPtrElementLess<TwitchAccount>>
accounts;
private:

View file

@ -8,7 +8,8 @@
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);
@ -37,7 +38,8 @@ void TwitchApi::findUserId(const QString user, std::function<void(QString)> succ
auto firstUser = users[0].toObject();
auto id = firstUser.value("_id");
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");
successCallback("");
return Failure;

View file

@ -7,7 +7,8 @@ namespace chatterino {
class TwitchApi
{
public:
static void findUserId(const QString user, std::function<void(QString)> callback);
static void findUserId(const QString user,
std::function<void(QString)> callback);
private:
};

View file

@ -19,7 +19,8 @@ void TwitchBadges::initialize(Settings &settings, Paths &paths)
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);
req.setCaller(QThread::currentThread());
@ -28,24 +29,29 @@ void TwitchBadges::loadTwitchBadges()
QJsonObject sets = root.value("badge_sets").toObject();
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);
++versionIt) {
auto emote =
Emote{{""},
ImageSet{
Image::fromUrl({root.value("image_url_1x").toString()}, 1),
Image::fromUrl({root.value("image_url_2x").toString()}, 0.5),
Image::fromUrl({root.value("image_url_4x").toString()}, 0.25),
},
Tooltip{root.value("description").toString()},
Url{root.value("clickURL").toString()}};
for (auto versionIt = std::begin(versions);
versionIt != std::end(versions); ++versionIt) {
auto emote = Emote{
{""},
ImageSet{
Image::fromUrl({root.value("image_url_1x").toString()},
1),
Image::fromUrl({root.value("image_url_2x").toString()},
0.5),
Image::fromUrl({root.value("image_url_4x").toString()},
0.25),
},
Tooltip{root.value("description").toString()},
Url{root.value("clickURL").toString()}};
// "title"
// "clickAction"
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->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);
// --
@ -84,15 +85,16 @@ bool TwitchChannel::canSendMessage() const
void TwitchChannel::refreshChannelEmotes()
{
loadBttvChannelEmotes(this->getName(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
if (auto shared = weak.lock()) //
*this->bttvEmotes_.access() = emoteMap;
});
getApp()->emotes->ffz.loadChannelEmotes(this->getName(),
[this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
if (auto shared = weak.lock())
*this->ffzEmotes_.access() = emoteMap;
});
loadBttvChannelEmotes(
this->getName(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
if (auto shared = weak.lock()) //
*this->bttvEmotes_.access() = emoteMap;
});
getApp()->emotes->ffz.loadChannelEmotes(
this->getName(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
if (auto shared = weak.lock())
*this->ffzEmotes_.access() = emoteMap;
});
}
void TwitchChannel::sendMessage(const QString &message)
@ -100,11 +102,11 @@ void TwitchChannel::sendMessage(const QString &message)
auto app = getApp();
if (!app->accounts->twitch.isLoggedIn()) {
// XXX: It would be nice if we could add a link here somehow that opened the "account
// manager" dialog
this->addMessage(
Message::createSystemMessage("You need to log in to send messages. You can "
"link your Twitch account in the settings."));
// XXX: It would be nice if we could add a link here somehow that opened
// the "account manager" dialog
this->addMessage(Message::createSystemMessage(
"You need to log in to send messages. You can "
"link your Twitch account in the settings."));
return;
}
@ -181,7 +183,8 @@ void TwitchChannel::addJoinedUser(const QString &user)
QTimer::singleShot(500, &this->lifetimeGuard_, [this] {
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;
joinedUsers->clear();
this->addMessage(message);
@ -208,7 +211,8 @@ void TwitchChannel::addPartedUser(const QString &user)
QTimer::singleShot(500, &this->lifetimeGuard_, [this] {
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;
this->addMessage(message);
partedUsers->clear();
@ -230,7 +234,8 @@ void TwitchChannel::setRoomId(const QString &id)
this->loadRecentMessages();
}
AccessGuard<const TwitchChannel::RoomModes> TwitchChannel::accessRoomModes() const
AccessGuard<const TwitchChannel::RoomModes> TwitchChannel::accessRoomModes()
const
{
return this->roomModes_.accessConst();
}
@ -247,12 +252,14 @@ bool TwitchChannel::isLive() const
return this->streamStatus_.access()->live;
}
AccessGuard<const TwitchChannel::StreamStatus> TwitchChannel::accessStreamStatus() const
AccessGuard<const TwitchChannel::StreamStatus>
TwitchChannel::accessStreamStatus() const
{
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 it = emotes->find(name);
@ -261,7 +268,8 @@ boost::optional<EmotePtr> TwitchChannel::getBttvEmote(const EmoteName &name) con
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 it = emotes->find(name);
@ -316,7 +324,8 @@ void TwitchChannel::refreshLiveStatus()
auto roomID = this->getRoomId();
if (roomID.isEmpty()) {
Log("[TwitchChannel:{}] Refreshing live status (Missing ID)", this->getName());
Log("[TwitchChannel:{}] Refreshing live status (Missing ID)",
this->getName());
this->setLive(false);
return;
}
@ -332,12 +341,13 @@ void TwitchChannel::refreshLiveStatus()
request.setCaller(QThread::currentThread());
//>>>>>>> 9bfbdefd2f0972a738230d5b95a009f73b1dd933
request.onSuccess([this, weak = this->weak_from_this()](auto result) -> Outcome {
ChannelPtr shared = weak.lock();
if (!shared) return Failure;
request.onSuccess(
[this, weak = this->weak_from_this()](auto result) -> Outcome {
ChannelPtr shared = weak.lock();
if (!shared) return Failure;
return this->parseLiveStatus(result.parseRapidJson());
});
return this->parseLiveStatus(result.parseRapidJson());
});
request.execute();
}
@ -362,8 +372,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
return Failure;
}
if (!stream.HasMember("viewers") || !stream.HasMember("game") || !stream.HasMember("channel") ||
!stream.HasMember("created_at")) {
if (!stream.HasMember("viewers") || !stream.HasMember("game") ||
!stream.HasMember("channel") || !stream.HasMember("created_at")) {
Log("[TwitchChannel:refreshLiveStatus] Missing members in stream");
this->setLive(false);
return Failure;
@ -372,7 +382,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
const rapidjson::Value &streamChannel = stream["channel"];
if (!streamChannel.IsObject() || !streamChannel.HasMember("status")) {
Log("[TwitchChannel:refreshLiveStatus] Missing member \"status\" in channel");
Log("[TwitchChannel:refreshLiveStatus] Missing member \"status\" in "
"channel");
return Failure;
}
@ -384,10 +395,11 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
status->viewerCount = stream["viewers"].GetUint();
status->game = stream["game"].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());
status->uptime =
QString::number(diff / 3600) + "h " + QString::number(diff % 3600 / 60) + "m";
status->uptime = QString::number(diff / 3600) + "h " +
QString::number(diff % 3600 / 60) + "m";
status->rerun = false;
if (stream.HasMember("stream_type")) {
@ -400,7 +412,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
const auto &broadcastPlatformValue = stream["broadcast_platform"];
if (broadcastPlatformValue.IsString()) {
const char *broadcastPlatform = stream["broadcast_platform"].GetString();
const char *broadcastPlatform =
stream["broadcast_platform"].GetString();
if (strcmp(broadcastPlatform, "rerun") == 0) {
status->rerun = true;
}
@ -417,18 +430,20 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
void TwitchChannel::loadRecentMessages()
{
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()));
request.makeAuthorizedV5(getDefaultClientID());
request.setCaller(QThread::currentThread());
request.onSuccess([this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
ChannelPtr shared = weak.lock();
if (!shared) return Failure;
request.onSuccess(
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
ChannelPtr shared = weak.lock();
if (!shared) return Failure;
return this->parseRecentMessages(result.parseJson());
});
return this->parseRecentMessages(result.parseJson());
});
request.execute();
}
@ -442,8 +457,8 @@ Outcome TwitchChannel::parseRecentMessages(const QJsonObject &jsonRoot)
for (const auto jsonMessage : jsonMessages) {
auto content = jsonMessage.toString().toUtf8();
// passing nullptr as the channel makes the message invalid but we don't check for that
// anyways
// passing nullptr as the channel makes the message invalid but we don't
// check for that anyways
auto message = Communi::IrcMessage::fromData(content, nullptr);
auto privMsg = dynamic_cast<Communi::IrcPrivateMessage *>(message);
assert(privMsg);
@ -468,7 +483,8 @@ void TwitchChannel::refreshPubsub()
if (roomId.isEmpty()) return;
auto account = getApp()->accounts->twitch.getCurrent();
getApp()->twitch2->pubsub->listenToChannelModerationActions(roomId, account);
getApp()->twitch2->pubsub->listenToChannelModerationActions(roomId,
account);
}
void TwitchChannel::refreshViewerList()
@ -477,35 +493,40 @@ void TwitchChannel::refreshViewerList()
const auto streamStatus = this->accessStreamStatus();
if (getSettings()->onlyFetchChattersForSmallerStreamers) {
if (streamStatus->live && streamStatus->viewerCount > getSettings()->smallStreamerLimit) {
if (streamStatus->live &&
streamStatus->viewerCount > getSettings()->smallStreamerLimit) {
return;
}
}
// 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.onSuccess([this, weak = this->weak_from_this()](auto result) -> Outcome {
// channel still exists?
auto shared = weak.lock();
if (!shared) return Failure;
request.onSuccess(
[this, weak = this->weak_from_this()](auto result) -> Outcome {
// channel still exists?
auto shared = weak.lock();
if (!shared) return Failure;
return this->parseViewerList(result.parseJson());
});
return this->parseViewerList(result.parseJson());
});
request.execute();
}
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
QJsonObject jsonCategories = jsonRoot.value("chatters").toObject();
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());
}
}
@ -515,8 +536,8 @@ Outcome TwitchChannel::parseViewerList(const QJsonObject &jsonRoot)
void TwitchChannel::loadBadges()
{
auto url = Url{"https://badges.twitch.tv/v1/badges/channels/" + this->getRoomId() +
"/display?language=en"};
auto url = Url{"https://badges.twitch.tv/v1/badges/channels/" +
this->getRoomId() + "/display?language=en"};
NetworkRequest req(url.string);
req.setCaller(QThread::currentThread());
@ -529,19 +550,24 @@ void TwitchChannel::loadBadges()
auto jsonRoot = result.parseJson();
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 _ = jsonBadgeSet->toObject()["versions"].toObject();
for (auto jsonVersion_ = _.begin(); jsonVersion_ != _.end(); jsonVersion_++) {
for (auto jsonVersion_ = _.begin(); jsonVersion_ != _.end();
jsonVersion_++) {
auto jsonVersion = jsonVersion_->toObject();
auto emote = std::make_shared<Emote>(
Emote{EmoteName{},
ImageSet{Image::fromUrl({jsonVersion["image_url_1x"].toString()}),
Image::fromUrl({jsonVersion["image_url_2x"].toString()}),
Image::fromUrl({jsonVersion["image_url_4x"].toString()})},
Tooltip{jsonRoot["description"].toString()},
Url{jsonVersion["clickURL"].toString()}});
auto emote = std::make_shared<Emote>(Emote{
EmoteName{},
ImageSet{Image::fromUrl(
{jsonVersion["image_url_1x"].toString()}),
Image::fromUrl(
{jsonVersion["image_url_2x"].toString()}),
Image::fromUrl(
{jsonVersion["image_url_4x"].toString()})},
Tooltip{jsonRoot["description"].toString()},
Url{jsonVersion["clickURL"].toString()}});
versions.emplace(jsonVersion_.key(), emote);
};
@ -555,63 +581,67 @@ void TwitchChannel::loadBadges()
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);
request.setCaller(QThread::currentThread());
request.onSuccess([this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson());
request.onSuccess(
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson());
for (auto &set : cheerEmoteSets) {
auto cheerEmoteSet = CheerEmoteSet();
cheerEmoteSet.regex = QRegularExpression("^" + set.prefix.toLower() + "([1-9][0-9]*)$");
for (auto &set : cheerEmoteSets) {
auto cheerEmoteSet = CheerEmoteSet();
cheerEmoteSet.regex = QRegularExpression(
"^" + set.prefix.toLower() + "([1-9][0-9]*)$");
for (auto &tier : set.tiers) {
CheerEmote cheerEmote;
for (auto &tier : set.tiers) {
CheerEmote cheerEmote;
cheerEmote.color = QColor(tier.color);
cheerEmote.minBits = tier.minBits;
cheerEmote.color = QColor(tier.color);
cheerEmote.minBits = tier.minBits;
// TODO(pajlada): We currently hardcode dark here :|
// We will continue to do so for now since we haven't had to
// solve that anywhere else
// TODO(pajlada): We currently hardcode dark here :|
// We will continue to do so for now since we haven't had to
// solve that anywhere else
cheerEmote.animatedEmote =
std::make_shared<Emote>(Emote{EmoteName{"cheer emote"},
ImageSet{
tier.images["dark"]["animated"]["1"],
tier.images["dark"]["animated"]["2"],
tier.images["dark"]["animated"]["4"],
},
Tooltip{}, Url{}});
cheerEmote.staticEmote =
std::make_shared<Emote>(Emote{EmoteName{"cheer emote"},
ImageSet{
tier.images["dark"]["static"]["1"],
tier.images["dark"]["static"]["2"],
tier.images["dark"]["static"]["4"],
},
Tooltip{}, Url{}});
cheerEmote.animatedEmote = std::make_shared<Emote>(
Emote{EmoteName{"cheer emote"},
ImageSet{
tier.images["dark"]["animated"]["1"],
tier.images["dark"]["animated"]["2"],
tier.images["dark"]["animated"]["4"],
},
Tooltip{}, Url{}});
cheerEmote.staticEmote = std::make_shared<Emote>(
Emote{EmoteName{"cheer emote"},
ImageSet{
tier.images["dark"]["static"]["1"],
tier.images["dark"]["static"]["2"],
tier.images["dark"]["static"]["4"],
},
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(),
[](const auto &lhs, const auto &rhs) {
return lhs.minBits < rhs.minBits; //
});
this->cheerEmoteSets_.emplace_back(cheerEmoteSet);
}
return Success;
});
return Success;
});
request.execute();
}
boost::optional<EmotePtr> TwitchChannel::getTwitchBadge(const QString &set,
const QString &version) const
boost::optional<EmotePtr> TwitchChannel::getTwitchBadge(
const QString &set, const QString &version) const
{
auto badgeSets = this->badgeSets_.access();
auto it = badgeSets->find(set);

Some files were not shown because too many files have changed in this diff Show more