mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
changed to 80 max column
This commit is contained in:
parent
defa7e41fa
commit
f71ff08e68
203 changed files with 3792 additions and 2405 deletions
|
@ -1,23 +1,14 @@
|
|||
IndentCaseLabels: true
|
||||
BasedOnStyle: Google
|
||||
IndentWidth: 4
|
||||
Standard: Auto
|
||||
PointerBindsToType: false
|
||||
Language: Cpp
|
||||
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
|
||||
|
|
|
@ -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,31 +119,39 @@ 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);
|
||||
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); });
|
||||
});
|
||||
|
||||
this->twitch.pubsub->signals_.moderation.modeChanged.connect([this](const auto &action) {
|
||||
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
|
||||
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") //
|
||||
QString text =
|
||||
QString("%1 turned %2 %3 mode") //
|
||||
.arg(action.source.name)
|
||||
.arg(action.state == ModeChangedAction::State::On ? "on" : "off")
|
||||
.arg(action.state == ModeChangedAction::State::On ? "on"
|
||||
: "off")
|
||||
.arg(action.getModeName());
|
||||
|
||||
if (action.duration > 0) {
|
||||
text.append(" (" + QString::number(action.duration) + " seconds)");
|
||||
text.append(" (" + QString::number(action.duration) +
|
||||
" seconds)");
|
||||
}
|
||||
|
||||
auto msg = Message::createSystemMessage(text);
|
||||
|
@ -150,7 +160,8 @@ void Application::initPubsub()
|
|||
|
||||
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,17 +169,21 @@ 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;
|
||||
|
@ -180,8 +195,10 @@ void Application::initPubsub()
|
|||
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;
|
||||
|
@ -196,10 +213,11 @@ void Application::initPubsub()
|
|||
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
class Resources2 : public Singleton {
|
||||
class Resources2 : public Singleton
|
||||
{
|
||||
public:
|
||||
Resources2();
|
||||
|
||||
|
|
|
@ -22,7 +22,8 @@ Channel::Channel(const QString &name, Type type)
|
|||
, name_(name)
|
||||
, type_(type)
|
||||
{
|
||||
QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout, [this]() {
|
||||
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);
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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 - ";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,8 +229,10 @@ void NetworkRequest::doRequest()
|
|||
};
|
||||
|
||||
if (data->caller_ != nullptr) {
|
||||
QObject::connect(worker, &NetworkWorker::doneUrl, data->caller_, handleReply);
|
||||
QObject::connect(reply, &QNetworkReply::finished, worker, [worker]() mutable {
|
||||
QObject::connect(worker, &NetworkWorker::doneUrl, data->caller_,
|
||||
handleReply);
|
||||
QObject::connect(reply, &QNetworkReply::finished, worker,
|
||||
[worker]() mutable {
|
||||
emit worker->doneUrl();
|
||||
|
||||
delete worker;
|
||||
|
@ -237,7 +247,8 @@ void NetworkRequest::doRequest()
|
|||
}
|
||||
};
|
||||
|
||||
QObject::connect(&requester, &NetworkRequester::requestUrl, worker, onUrlRequested);
|
||||
QObject::connect(&requester, &NetworkRequester::requestUrl, worker,
|
||||
onUrlRequested);
|
||||
|
||||
emit requester.requestUrl();
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
explicit NetworkRequest(
|
||||
const std::string &url,
|
||||
NetworkRequestType requestType = NetworkRequestType::Get);
|
||||
explicit NetworkRequest(QUrl 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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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_);
|
||||
|
|
|
@ -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 (...) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -20,9 +20,10 @@
|
|||
|
||||
#define TWITCH_DEFAULT_COMMANDS \
|
||||
{ \
|
||||
"/help", "/w", "/me", "/disconnect", "/mods", "/color", "/ban", "/unban", "/timeout", \
|
||||
"/untimeout", "/slow", "/slowoff", "/r9kbeta", "/r9kbetaoff", "/emoteonly", \
|
||||
"/emoteonlyoff", "/clear", "/subscribers", "/subscribersoff", "/followers", \
|
||||
"/help", "/w", "/me", "/disconnect", "/mods", "/color", "/ban", \
|
||||
"/unban", "/timeout", "/untimeout", "/slow", "/slowoff", \
|
||||
"/r9kbeta", "/r9kbetaoff", "/emoteonly", "/emoteonlyoff", \
|
||||
"/clear", "/subscribers", "/subscribersoff", "/followers", \
|
||||
"/followersoff" \
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
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) {
|
||||
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));
|
||||
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) {
|
||||
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));
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -15,7 +15,8 @@ class HighlightBlacklistModel : public SignalVectorModel<HighlightBlacklistUser>
|
|||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual HighlightBlacklistUser getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
virtual HighlightBlacklistUser getItemFromRow(
|
||||
std::vector<QStandardItem *> &row,
|
||||
const HighlightBlacklistUser &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
|
|
|
@ -16,13 +16,15 @@ 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 |
|
||||
, regex_(isRegex ? pattern : "",
|
||||
QRegularExpression::CaseInsensitiveOption |
|
||||
QRegularExpression::UseUnicodePropertiesOption)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -15,7 +15,8 @@ class HighlightModel : public SignalVectorModel<HighlightPhrase>
|
|||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
virtual HighlightPhrase getItemFromRow(
|
||||
std::vector<QStandardItem *> &row,
|
||||
const HighlightPhrase &original) override;
|
||||
|
||||
// turns a row in the model into a vector 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;
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -15,7 +15,8 @@ class UserHighlightModel : public SignalVectorModel<HighlightPhrase>
|
|||
|
||||
protected:
|
||||
// vector into model row
|
||||
virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
virtual HighlightPhrase getItemFromRow(
|
||||
std::vector<QStandardItem *> &row,
|
||||
const HighlightPhrase &original) override;
|
||||
|
||||
virtual void getRowFromItem(const HighlightPhrase &item,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@ public:
|
|||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual ModerationAction getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
virtual ModerationAction getItemFromRow(
|
||||
std::vector<QStandardItem *> &row,
|
||||
const ModerationAction &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,7 +208,8 @@ public:
|
|||
{
|
||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||
|
||||
return LimitedQueueSnapshot<T>(this->chunks_, this->limit_ - this->space(),
|
||||
return LimitedQueueSnapshot<T>(
|
||||
this->chunks_, this->limit_ - this->space(),
|
||||
this->firstChunkOffset_, this->lastChunkEnd_);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
|
|
@ -12,7 +12,8 @@ class LimitedQueueSnapshot
|
|||
public:
|
||||
LimitedQueueSnapshot() = default;
|
||||
|
||||
LimitedQueueSnapshot(std::shared_ptr<std::vector<std::shared_ptr<std::vector<T>>>> chunks,
|
||||
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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
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>;
|
||||
|
|
|
@ -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://");
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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))
|
||||
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())));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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()
|
||||
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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,7 +96,8 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element, bool for
|
|||
int newLineHeight = element->getRect().height();
|
||||
|
||||
// compact emote offset
|
||||
bool isCompactEmote = !(this->flags_ & Message::DisableCompactEmotes) &&
|
||||
bool isCompactEmote =
|
||||
!(this->flags_ & Message::DisableCompactEmotes) &&
|
||||
element->getCreator().getFlags() & MessageElement::EmoteImages;
|
||||
|
||||
if (isCompactEmote) {
|
||||
|
@ -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,13 +127,17 @@ 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) &&
|
||||
bool isCompactEmote =
|
||||
!(this->flags_ & Message::DisableCompactEmotes) &&
|
||||
element->getCreator().getFlags() & MessageElement::EmoteImages;
|
||||
|
||||
int yExtra = 0;
|
||||
|
@ -134,12 +145,14 @@ void MessageLayoutContainer::breakLine()
|
|||
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->setPosition(
|
||||
QPoint(element->getRect().x() + xOffset + this->margin.left,
|
||||
element->getRect().y() + this->lineHeight_ + yExtra));
|
||||
}
|
||||
|
||||
|
@ -147,7 +160,8 @@ void MessageLayoutContainer::breakLine()
|
|||
this->lines_.back().endIndex = this->lineStart_;
|
||||
this->lines_.back().endCharIndex = this->charIndex_;
|
||||
}
|
||||
this->lines_.push_back({(int)lineStart_, 0, this->charIndex_, 0,
|
||||
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++) {
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,10 +263,11 @@ 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()),
|
||||
painter.drawText(QPoint(this->getRect().x(),
|
||||
this->getRect().y() + this->getRect().height()),
|
||||
this->line2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,19 +78,22 @@ void BttvEmotes::loadGlobalEmotes()
|
|||
request.execute();
|
||||
}
|
||||
|
||||
std::pair<Outcome, EmoteMap> BttvEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot,
|
||||
const EmoteMap ¤tEmotes)
|
||||
std::pair<Outcome, EmoteMap> BttvEmotes::parseGlobalEmotes(
|
||||
const QJsonObject &jsonRoot, const EmoteMap ¤tEmotes)
|
||||
{
|
||||
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),
|
||||
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"},
|
||||
|
|
|
@ -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 ¤tEmotes);
|
||||
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
|
||||
const QJsonObject &jsonRoot, const EmoteMap ¤tEmotes);
|
||||
|
||||
UniqueAccess<EmoteMap> globalEmotes_;
|
||||
// UniqueAccess<WeakEmoteIdMap> channelEmoteCache_;
|
||||
|
|
|
@ -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,8 +54,10 @@ 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),
|
||||
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"},
|
||||
|
@ -59,18 +68,21 @@ static std::pair<Outcome, EmoteMap> bttvParseChannelEmotes(const QJsonObject &js
|
|||
// 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()}},
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
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)},
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
||||
|
|
|
@ -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,8 +92,10 @@ 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")
|
||||
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)};
|
||||
|
||||
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -27,7 +27,8 @@ IrcConnection::IrcConnection(QObject *parent)
|
|||
}
|
||||
});
|
||||
|
||||
QObject::connect(this, &Communi::IrcConnection::messageReceived, [this](Communi::IrcMessage *) {
|
||||
QObject::connect(this, &Communi::IrcConnection::messageReceived,
|
||||
[this](Communi::IrcMessage *) {
|
||||
this->recentlyReceivedMessage_ = true;
|
||||
|
||||
if (this->reconnectTimer_.isActive()) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,8 +310,10 @@ 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) {
|
||||
// Notice wasn't targeted at a single channel, send to all twitch
|
||||
// channels
|
||||
app->twitch.server->forEachChannelAndSpecialChannels(
|
||||
[msg](const auto &c) {
|
||||
c->addMessage(msg); //
|
||||
});
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,7 +137,8 @@ void PubSubClient::ping()
|
|||
|
||||
auto self = this->shared_from_this();
|
||||
|
||||
runAfter(this->websocketClient_.get_io_service(), std::chrono::seconds(15), [self](auto timer) {
|
||||
runAfter(this->websocketClient_.get_io_service(), std::chrono::seconds(15),
|
||||
[self](auto timer) {
|
||||
if (!self->started_) {
|
||||
return;
|
||||
}
|
||||
|
@ -146,7 +149,8 @@ void PubSubClient::ping()
|
|||
}
|
||||
});
|
||||
|
||||
runAfter(this->websocketClient_.get_io_service(), std::chrono::minutes(5), [self](auto timer) {
|
||||
runAfter(this->websocketClient_.get_io_service(), std::chrono::minutes(5),
|
||||
[self](auto timer) {
|
||||
if (!self->started_) {
|
||||
return;
|
||||
}
|
||||
|
@ -158,11 +162,13 @@ void PubSubClient::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,26 +257,28 @@ PubSub::PubSub()
|
|||
this->signals_.moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["subscribersoff"] = [this](const auto &data,
|
||||
this->moderationActionHandlers["subscribersoff"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::SubscribersOnly;
|
||||
action.state = ModeChangedAction::State::Off;
|
||||
|
||||
this->signals_.moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["subscribers"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::SubscribersOnly;
|
||||
action.state = ModeChangedAction::State::Off;
|
||||
|
||||
this->signals_.moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["subscribers"] = [this](const auto &data, const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::SubscribersOnly;
|
||||
action.state = ModeChangedAction::State::On;
|
||||
|
||||
this->signals_.moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["emoteonlyoff"] = [this](const auto &data, const auto &roomID) {
|
||||
this->moderationActionHandlers["emoteonlyoff"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::EmoteOnly;
|
||||
|
@ -274,7 +287,8 @@ PubSub::PubSub()
|
|||
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 |
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -31,7 +31,8 @@ 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,
|
||||
rapidjson::Document createListenMessage(
|
||||
const std::vector<std::string> &topicsVec,
|
||||
std::shared_ptr<TwitchAccount> account)
|
||||
{
|
||||
rapidjson::Document msg(rapidjson::kObjectType);
|
||||
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
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 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);
|
||||
|
||||
|
|
|
@ -20,10 +20,12 @@ EmoteName cleanUpCode(const EmoteName &dirtyEmoteCode)
|
|||
cleanCode.detach();
|
||||
|
||||
static QMap<QString, QString> emoteNameReplacements{
|
||||
{"[oO](_|\\.)[oO]", "O_o"}, {"\\>\\;\\(", ">("}, {"\\<\\;3", "<3"},
|
||||
{"\\:-?(o|O)", ":O"}, {"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"},
|
||||
{"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("}, {"\\:-?\\)", ":)"},
|
||||
{"\\:-?D", ":D"}, {"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"},
|
||||
{"[oO](_|\\.)[oO]", "O_o"}, {"\\>\\;\\(", ">("},
|
||||
{"\\<\\;3", "<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,27 +158,31 @@ void TwitchAccount::loadIgnores()
|
|||
req.execute();
|
||||
}
|
||||
|
||||
void TwitchAccount::ignore(const QString &targetName,
|
||||
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,
|
||||
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 " +
|
||||
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,
|
||||
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;
|
||||
|
|
|
@ -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,
|
||||
void ignoreByID(
|
||||
const QString &targetUserID, const QString &targetName,
|
||||
std::function<void(IgnoreResult, const QString &)> onFinished);
|
||||
void unignore(const QString &targetName,
|
||||
void unignore(
|
||||
const QString &targetName,
|
||||
std::function<void(UnignoreResult, const QString &)> onFinished);
|
||||
void unignoreByID(const QString &targetUserID, const QString &targetName,
|
||||
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;
|
||||
|
||||
|
|
|
@ -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,7 +193,8 @@ TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
|
|||
}
|
||||
}
|
||||
|
||||
auto newUser = std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
|
||||
auto newUser =
|
||||
std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
|
||||
userData.clientID, userData.userID);
|
||||
|
||||
// std::lock_guard<std::mutex> lock(this->mutex);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
};
|
||||
|
|
|
@ -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,16 +29,20 @@ 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{{""},
|
||||
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),
|
||||
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()}};
|
||||
|
@ -45,7 +50,8 @@ void TwitchBadges::loadTwitchBadges()
|
|||
// "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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,12 +85,13 @@ bool TwitchChannel::canSendMessage() const
|
|||
|
||||
void TwitchChannel::refreshChannelEmotes()
|
||||
{
|
||||
loadBttvChannelEmotes(this->getName(), [this, weak = weakOf<Channel>(this)](auto &&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) {
|
||||
getApp()->emotes->ffz.loadChannelEmotes(
|
||||
this->getName(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
|
||||
if (auto shared = weak.lock())
|
||||
*this->ffzEmotes_.access() = emoteMap;
|
||||
});
|
||||
|
@ -100,10 +102,10 @@ 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 "
|
||||
// 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,7 +341,8 @@ void TwitchChannel::refreshLiveStatus()
|
|||
request.setCaller(QThread::currentThread());
|
||||
//>>>>>>> 9bfbdefd2f0972a738230d5b95a009f73b1dd933
|
||||
|
||||
request.onSuccess([this, weak = this->weak_from_this()](auto result) -> Outcome {
|
||||
request.onSuccess(
|
||||
[this, weak = this->weak_from_this()](auto result) -> Outcome {
|
||||
ChannelPtr shared = weak.lock();
|
||||
if (!shared) return Failure;
|
||||
|
||||
|
@ -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,13 +430,15 @@ 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 {
|
||||
request.onSuccess(
|
||||
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||
ChannelPtr shared = weak.lock();
|
||||
if (!shared) return Failure;
|
||||
|
||||
|
@ -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,16 +493,19 @@ 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 {
|
||||
request.onSuccess(
|
||||
[this, weak = this->weak_from_this()](auto result) -> Outcome {
|
||||
// channel still exists?
|
||||
auto shared = weak.lock();
|
||||
if (!shared) return Failure;
|
||||
|
@ -499,13 +518,15 @@ void TwitchChannel::refreshViewerList()
|
|||
|
||||
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,17 +550,22 @@ 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()})},
|
||||
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()}});
|
||||
|
||||
|
@ -555,16 +581,19 @@ 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 {
|
||||
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]*)$");
|
||||
cheerEmoteSet.regex = QRegularExpression(
|
||||
"^" + set.prefix.toLower() + "([1-9][0-9]*)$");
|
||||
|
||||
for (auto &tier : set.tiers) {
|
||||
CheerEmote cheerEmote;
|
||||
|
@ -576,16 +605,16 @@ void TwitchChannel::loadCheerEmotes()
|
|||
// 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"},
|
||||
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"},
|
||||
cheerEmote.staticEmote = std::make_shared<Emote>(
|
||||
Emote{EmoteName{"cheer emote"},
|
||||
ImageSet{
|
||||
tier.images["dark"]["static"]["1"],
|
||||
tier.images["dark"]["static"]["2"],
|
||||
|
@ -596,7 +625,8 @@ void TwitchChannel::loadCheerEmotes()
|
|||
cheerEmoteSet.cheerEmotes.emplace_back(cheerEmote);
|
||||
}
|
||||
|
||||
std::sort(cheerEmoteSet.cheerEmotes.begin(), cheerEmoteSet.cheerEmotes.end(),
|
||||
std::sort(cheerEmoteSet.cheerEmotes.begin(),
|
||||
cheerEmoteSet.cheerEmotes.end(),
|
||||
[](const auto &lhs, const auto &rhs) {
|
||||
return lhs.minBits < rhs.minBits; //
|
||||
});
|
||||
|
@ -610,8 +640,8 @@ void TwitchChannel::loadCheerEmotes()
|
|||
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
Loading…
Reference in a new issue