added brace wrapping after if and for
This commit is contained in:
@@ -23,12 +23,16 @@ void LinkResolver::getLinkInfo(
|
||||
auto statusCode = root.value("status").toInt();
|
||||
QString response = QString();
|
||||
QString linkString = url;
|
||||
if (statusCode == 200) {
|
||||
if (statusCode == 200)
|
||||
{
|
||||
response = root.value("tooltip").toString();
|
||||
if (getSettings()->enableUnshortLinks) {
|
||||
if (getSettings()->enableUnshortLinks)
|
||||
{
|
||||
linkString = root.value("link").toString();
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
response = root.value("message").toString();
|
||||
}
|
||||
successCallback(QUrl::fromPercentEncoding(response.toUtf8()),
|
||||
|
||||
@@ -11,7 +11,7 @@ class LinkResolver
|
||||
{
|
||||
public:
|
||||
static void getLinkInfo(const QString url,
|
||||
std::function<void(QString, Link)> callback);
|
||||
std::function<void(QString, Link)> callback);
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
@@ -29,7 +29,8 @@ namespace {
|
||||
auto urlTemplate =
|
||||
qS("https:") + jsonRoot.value("urlTemplate").toString();
|
||||
|
||||
for (auto jsonEmote : jsonEmotes) {
|
||||
for (auto jsonEmote : jsonEmotes)
|
||||
{
|
||||
auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
|
||||
auto name =
|
||||
EmoteName{jsonEmote.toObject().value("code").toString()};
|
||||
@@ -62,7 +63,8 @@ namespace {
|
||||
auto jsonEmotes = jsonRoot.value("emotes").toArray();
|
||||
auto urlTemplate = "https:" + jsonRoot.value("urlTemplate").toString();
|
||||
|
||||
for (auto jsonEmote_ : jsonEmotes) {
|
||||
for (auto jsonEmote_ : jsonEmotes)
|
||||
{
|
||||
auto jsonEmote = jsonEmote_.toObject();
|
||||
|
||||
auto id = EmoteId{jsonEmote.value("id").toString()};
|
||||
@@ -103,7 +105,8 @@ boost::optional<EmotePtr> BttvEmotes::emote(const EmoteName &name) const
|
||||
auto emotes = this->global_.get();
|
||||
auto it = emotes->find(name);
|
||||
|
||||
if (it == emotes->end()) return boost::none;
|
||||
if (it == emotes->end())
|
||||
return boost::none;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
@@ -137,7 +140,8 @@ void BttvEmotes::loadChannel(const QString &channelName,
|
||||
|
||||
request.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
|
||||
auto pair = parseChannelEmotes(result.parseJson());
|
||||
if (pair.first) callback(std::move(pair.second));
|
||||
if (pair.first)
|
||||
callback(std::move(pair.second));
|
||||
return pair.first;
|
||||
});
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ ChatterinoBadges::ChatterinoBadges()
|
||||
boost::optional<EmotePtr> ChatterinoBadges::getBadge(const UserName &username)
|
||||
{
|
||||
auto it = badgeMap.find(username.string);
|
||||
if (it != badgeMap.end()) {
|
||||
if (it != badgeMap.end())
|
||||
{
|
||||
return emotes[it->second];
|
||||
}
|
||||
return boost::none;
|
||||
@@ -41,7 +42,8 @@ void ChatterinoBadges::loadChatterinoBadges()
|
||||
req.onSuccess([this](auto result) -> Outcome {
|
||||
auto jsonRoot = result.parseJson();
|
||||
int index = 0;
|
||||
for (const auto &jsonBadge_ : jsonRoot.value("badges").toArray()) {
|
||||
for (const auto &jsonBadge_ : jsonRoot.value("badges").toArray())
|
||||
{
|
||||
auto jsonBadge = jsonBadge_.toObject();
|
||||
auto emote = Emote{
|
||||
EmoteName{}, ImageSet{Url{jsonBadge.value("image").toString()}},
|
||||
@@ -49,7 +51,8 @@ void ChatterinoBadges::loadChatterinoBadges()
|
||||
|
||||
emotes.push_back(std::make_shared<const Emote>(std::move(emote)));
|
||||
|
||||
for (const auto &user : jsonBadge.value("users").toArray()) {
|
||||
for (const auto &user : jsonBadge.value("users").toArray())
|
||||
{
|
||||
badgeMap[user.toString()] = index;
|
||||
}
|
||||
++index;
|
||||
|
||||
@@ -29,11 +29,15 @@ namespace {
|
||||
bool messenger;
|
||||
} capabilities;
|
||||
|
||||
if (!shortCode.isEmpty()) {
|
||||
if (!shortCode.isEmpty())
|
||||
{
|
||||
emojiData->shortCodes.push_back(shortCode);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto &shortCodes = unparsedEmoji["short_names"];
|
||||
for (const auto &shortCode : shortCodes.GetArray()) {
|
||||
for (const auto &shortCode : shortCodes.GetArray())
|
||||
{
|
||||
emojiData->shortCodes.emplace_back(shortCode.GetString());
|
||||
}
|
||||
}
|
||||
@@ -49,39 +53,50 @@ namespace {
|
||||
rj::getSafe(unparsedEmoji, "has_img_facebook", capabilities.facebook);
|
||||
rj::getSafe(unparsedEmoji, "has_img_messenger", capabilities.messenger);
|
||||
|
||||
if (capabilities.apple) {
|
||||
if (capabilities.apple)
|
||||
{
|
||||
emojiData->capabilities.insert("Apple");
|
||||
}
|
||||
if (capabilities.google) {
|
||||
if (capabilities.google)
|
||||
{
|
||||
emojiData->capabilities.insert("Google");
|
||||
}
|
||||
if (capabilities.twitter) {
|
||||
if (capabilities.twitter)
|
||||
{
|
||||
emojiData->capabilities.insert("Twitter");
|
||||
}
|
||||
if (capabilities.emojione) {
|
||||
if (capabilities.emojione)
|
||||
{
|
||||
emojiData->capabilities.insert("EmojiOne 3");
|
||||
}
|
||||
if (capabilities.facebook) {
|
||||
if (capabilities.facebook)
|
||||
{
|
||||
emojiData->capabilities.insert("Facebook");
|
||||
}
|
||||
if (capabilities.messenger) {
|
||||
if (capabilities.messenger)
|
||||
{
|
||||
emojiData->capabilities.insert("Messenger");
|
||||
}
|
||||
|
||||
QStringList unicodeCharacters;
|
||||
if (!emojiData->nonQualifiedCode.isEmpty()) {
|
||||
if (!emojiData->nonQualifiedCode.isEmpty())
|
||||
{
|
||||
unicodeCharacters =
|
||||
emojiData->nonQualifiedCode.toLower().split('-');
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
unicodeCharacters = emojiData->unifiedCode.toLower().split('-');
|
||||
}
|
||||
if (unicodeCharacters.length() < 1) {
|
||||
if (unicodeCharacters.length() < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int numUnicodeBytes = 0;
|
||||
|
||||
for (const QString &unicodeCharacter : unicodeCharacters) {
|
||||
for (const QString &unicodeCharacter : unicodeCharacters)
|
||||
{
|
||||
unicodeBytes[numUnicodeBytes++] =
|
||||
QString(unicodeCharacter).toUInt(nullptr, 16);
|
||||
}
|
||||
@@ -115,17 +130,20 @@ void Emojis::loadEmojis()
|
||||
rapidjson::Document root;
|
||||
rapidjson::ParseResult result = root.Parse(data.toUtf8(), data.length());
|
||||
|
||||
if (result.Code() != rapidjson::kParseErrorNone) {
|
||||
if (result.Code() != rapidjson::kParseErrorNone)
|
||||
{
|
||||
log("JSON parse error: {} ({})",
|
||||
rapidjson::GetParseError_En(result.Code()), result.Offset());
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &unparsedEmoji : root.GetArray()) {
|
||||
for (const auto &unparsedEmoji : root.GetArray())
|
||||
{
|
||||
auto emojiData = std::make_shared<EmojiData>();
|
||||
parseEmoji(emojiData, unparsedEmoji);
|
||||
|
||||
for (const auto &shortCode : emojiData->shortCodes) {
|
||||
for (const auto &shortCode : emojiData->shortCodes)
|
||||
{
|
||||
this->emojiShortCodeToEmoji_.insert(shortCode, emojiData);
|
||||
this->shortCodes.emplace_back(shortCode);
|
||||
}
|
||||
@@ -134,16 +152,19 @@ void Emojis::loadEmojis()
|
||||
|
||||
this->emojis.insert(emojiData->unifiedCode, emojiData);
|
||||
|
||||
if (unparsedEmoji.HasMember("skin_variations")) {
|
||||
if (unparsedEmoji.HasMember("skin_variations"))
|
||||
{
|
||||
for (const auto &skinVariation :
|
||||
unparsedEmoji["skin_variations"].GetObject()) {
|
||||
unparsedEmoji["skin_variations"].GetObject())
|
||||
{
|
||||
std::string tone = skinVariation.name.GetString();
|
||||
const auto &variation = skinVariation.value;
|
||||
|
||||
auto variationEmojiData = std::make_shared<EmojiData>();
|
||||
|
||||
auto toneNameIt = toneNames.find(tone);
|
||||
if (toneNameIt == toneNames.end()) {
|
||||
if (toneNameIt == toneNames.end())
|
||||
{
|
||||
log("Tone with key {} does not exist in tone names map",
|
||||
tone);
|
||||
continue;
|
||||
@@ -172,24 +193,28 @@ void Emojis::loadEmojiOne2Capabilities()
|
||||
file.open(QFile::ReadOnly);
|
||||
QTextStream in(&file);
|
||||
|
||||
while (!in.atEnd()) {
|
||||
while (!in.atEnd())
|
||||
{
|
||||
// Line example: sunglasses 1f60e
|
||||
QString line = in.readLine();
|
||||
|
||||
if (line.at(0) == '#') {
|
||||
if (line.at(0) == '#')
|
||||
{
|
||||
// Ignore lines starting with # (comments)
|
||||
continue;
|
||||
}
|
||||
|
||||
QStringList parts = line.split(' ');
|
||||
if (parts.length() < 2) {
|
||||
if (parts.length() < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString shortCode = parts[0];
|
||||
|
||||
auto emojiIt = this->emojiShortCodeToEmoji_.find(shortCode);
|
||||
if (emojiIt != this->emojiShortCodeToEmoji_.end()) {
|
||||
if (emojiIt != this->emojiShortCodeToEmoji_.end())
|
||||
{
|
||||
std::shared_ptr<EmojiData> emoji = *emojiIt;
|
||||
emoji->capabilities.insert("EmojiOne 2");
|
||||
continue;
|
||||
@@ -199,7 +224,8 @@ void Emojis::loadEmojiOne2Capabilities()
|
||||
|
||||
void Emojis::sortEmojis()
|
||||
{
|
||||
for (auto &p : this->emojiFirstByte_) {
|
||||
for (auto &p : this->emojiFirstByte_)
|
||||
{
|
||||
std::stable_sort(p.begin(), p.end(),
|
||||
[](const auto &lhs, const auto &rhs) {
|
||||
return lhs->value.length() > rhs->value.length();
|
||||
@@ -239,13 +265,16 @@ void Emojis::loadEmojiSet()
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
if (emoji->capabilities.count(emojiSetToUse) == 0) {
|
||||
if (emoji->capabilities.count(emojiSetToUse) == 0)
|
||||
{
|
||||
emojiSetToUse = "EmojiOne 3";
|
||||
}
|
||||
|
||||
QString code = emoji->unifiedCode;
|
||||
if (emojiSetToUse == "EmojiOne 2") {
|
||||
if (!emoji->nonQualifiedCode.isEmpty()) {
|
||||
if (emojiSetToUse == "EmojiOne 2")
|
||||
{
|
||||
if (!emoji->nonQualifiedCode.isEmpty())
|
||||
{
|
||||
code = emoji->nonQualifiedCode;
|
||||
}
|
||||
}
|
||||
@@ -253,7 +282,8 @@ void Emojis::loadEmojiSet()
|
||||
QString urlPrefix = "https://cdnjs.cloudflare.com/ajax/libs/"
|
||||
"emojione/2.2.6/assets/png/";
|
||||
auto it = emojiSets.find(emojiSetToUse);
|
||||
if (it != emojiSets.end()) {
|
||||
if (it != emojiSets.end())
|
||||
{
|
||||
urlPrefix = it->second;
|
||||
}
|
||||
QString url = urlPrefix + code + ".png";
|
||||
@@ -270,15 +300,18 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
|
||||
auto result = std::vector<boost::variant<EmotePtr, QString>>();
|
||||
int lastParsedEmojiEndIndex = 0;
|
||||
|
||||
for (auto i = 0; i < text.length(); ++i) {
|
||||
for (auto i = 0; i < text.length(); ++i)
|
||||
{
|
||||
const QChar character = text.at(i);
|
||||
|
||||
if (character.isLowSurrogate()) {
|
||||
if (character.isLowSurrogate())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = this->emojiFirstByte_.find(character);
|
||||
if (it == this->emojiFirstByte_.end()) {
|
||||
if (it == this->emojiFirstByte_.end())
|
||||
{
|
||||
// No emoji starts with this character
|
||||
continue;
|
||||
}
|
||||
@@ -291,24 +324,29 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
|
||||
|
||||
int matchedEmojiLength = 0;
|
||||
|
||||
for (const std::shared_ptr<EmojiData> &emoji : possibleEmojis) {
|
||||
for (const std::shared_ptr<EmojiData> &emoji : possibleEmojis)
|
||||
{
|
||||
int emojiExtraCharacters = emoji->value.length() - 1;
|
||||
if (emojiExtraCharacters > remainingCharacters) {
|
||||
if (emojiExtraCharacters > remainingCharacters)
|
||||
{
|
||||
// It cannot be this emoji, there's not enough space for it
|
||||
continue;
|
||||
}
|
||||
|
||||
bool match = true;
|
||||
|
||||
for (int j = 1; j < emoji->value.length(); ++j) {
|
||||
if (text.at(i + j) != emoji->value.at(j)) {
|
||||
for (int j = 1; j < emoji->value.length(); ++j)
|
||||
{
|
||||
if (text.at(i + j) != emoji->value.at(j))
|
||||
{
|
||||
match = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
if (match)
|
||||
{
|
||||
matchedEmoji = emoji;
|
||||
matchedEmojiLength = emoji->value.length();
|
||||
|
||||
@@ -316,7 +354,8 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedEmojiLength == 0) {
|
||||
if (matchedEmojiLength == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -326,7 +365,8 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
|
||||
int charactersFromLastParsedEmoji =
|
||||
currentParsedEmojiFirstIndex - lastParsedEmojiEndIndex;
|
||||
|
||||
if (charactersFromLastParsedEmoji > 0) {
|
||||
if (charactersFromLastParsedEmoji > 0)
|
||||
{
|
||||
// Add characters inbetween emojis
|
||||
result.emplace_back(text.mid(lastParsedEmojiEndIndex,
|
||||
charactersFromLastParsedEmoji));
|
||||
@@ -340,7 +380,8 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
|
||||
i += matchedEmojiLength - 1;
|
||||
}
|
||||
|
||||
if (lastParsedEmojiEndIndex < text.length()) {
|
||||
if (lastParsedEmojiEndIndex < text.length())
|
||||
{
|
||||
// Add remaining characters
|
||||
result.emplace_back(text.mid(lastParsedEmojiEndIndex));
|
||||
}
|
||||
@@ -355,7 +396,8 @@ QString Emojis::replaceShortCodes(const QString &text)
|
||||
|
||||
int32_t offset = 0;
|
||||
|
||||
while (it.hasNext()) {
|
||||
while (it.hasNext())
|
||||
{
|
||||
auto match = it.next();
|
||||
|
||||
auto capturedString = match.captured();
|
||||
@@ -365,7 +407,8 @@ QString Emojis::replaceShortCodes(const QString &text)
|
||||
|
||||
auto emojiIt = this->emojiShortCodeToEmoji_.constFind(matchString);
|
||||
|
||||
if (emojiIt == this->emojiShortCodeToEmoji_.constEnd()) {
|
||||
if (emojiIt == this->emojiShortCodeToEmoji_.constEnd())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ namespace {
|
||||
Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
|
||||
{
|
||||
auto emote = urls.value(emoteScale);
|
||||
if (emote.isUndefined()) {
|
||||
if (emote.isUndefined())
|
||||
{
|
||||
return {""};
|
||||
}
|
||||
|
||||
@@ -48,10 +49,12 @@ namespace {
|
||||
auto jsonSets = jsonRoot.value("sets").toObject();
|
||||
auto emotes = EmoteMap();
|
||||
|
||||
for (auto jsonSet : jsonSets) {
|
||||
for (auto jsonSet : jsonSets)
|
||||
{
|
||||
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
|
||||
|
||||
for (auto jsonEmoteValue : jsonEmotes) {
|
||||
for (auto jsonEmoteValue : jsonEmotes)
|
||||
{
|
||||
auto jsonEmote = jsonEmoteValue.toObject();
|
||||
|
||||
auto name = EmoteName{jsonEmote.value("name").toString()};
|
||||
@@ -78,10 +81,12 @@ namespace {
|
||||
auto jsonSets = jsonRoot.value("sets").toObject();
|
||||
auto emotes = EmoteMap();
|
||||
|
||||
for (auto jsonSet : jsonSets) {
|
||||
for (auto jsonSet : jsonSets)
|
||||
{
|
||||
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
|
||||
|
||||
for (auto _jsonEmote : jsonEmotes) {
|
||||
for (auto _jsonEmote : jsonEmotes)
|
||||
{
|
||||
auto jsonEmote = _jsonEmote.toObject();
|
||||
|
||||
// margins
|
||||
@@ -120,7 +125,8 @@ boost::optional<EmotePtr> FfzEmotes::emote(const EmoteName &name) const
|
||||
{
|
||||
auto emotes = this->global_.get();
|
||||
auto it = emotes->find(name);
|
||||
if (it != emotes->end()) return it->second;
|
||||
if (it != emotes->end())
|
||||
return it->second;
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
@@ -156,17 +162,20 @@ void FfzEmotes::loadChannel(const QString &channelName,
|
||||
|
||||
request.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
|
||||
auto pair = parseChannelEmotes(result.parseJson());
|
||||
if (pair.first) callback(std::move(pair.second));
|
||||
if (pair.first)
|
||||
callback(std::move(pair.second));
|
||||
return pair.first;
|
||||
});
|
||||
|
||||
request.onError([channelName](int result) {
|
||||
if (result == 203) {
|
||||
if (result == 203)
|
||||
{
|
||||
// User does not have any FFZ emotes
|
||||
return true;
|
||||
}
|
||||
|
||||
if (result == -2) {
|
||||
if (result == -2)
|
||||
{
|
||||
// TODO: Auto retry in case of a timeout, with a delay
|
||||
log("Fetching FFZ emotes for channel {} failed due to timeout",
|
||||
channelName);
|
||||
|
||||
@@ -60,7 +60,8 @@ AbstractIrcServer::AbstractIrcServer()
|
||||
this->falloffCounter_ =
|
||||
std::min(MAX_FALLOFF_COUNTER, this->falloffCounter_ + 1);
|
||||
|
||||
if (!this->readConnection_->isConnected()) {
|
||||
if (!this->readConnection_->isConnected())
|
||||
{
|
||||
log("Trying to reconnect... {}", this->falloffCounter_);
|
||||
this->connect();
|
||||
}
|
||||
@@ -73,10 +74,13 @@ void AbstractIrcServer::connect()
|
||||
|
||||
bool separateWriteConnection = this->hasSeparateWriteConnection();
|
||||
|
||||
if (separateWriteConnection) {
|
||||
if (separateWriteConnection)
|
||||
{
|
||||
this->initializeConnection(this->writeConnection_.get(), false, true);
|
||||
this->initializeConnection(this->readConnection_.get(), true, false);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
this->initializeConnection(this->readConnection_.get(), true, true);
|
||||
}
|
||||
|
||||
@@ -85,8 +89,10 @@ void AbstractIrcServer::connect()
|
||||
std::lock_guard<std::mutex> lock1(this->connectionMutex_);
|
||||
std::lock_guard<std::mutex> lock2(this->channelMutex);
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values()) {
|
||||
if (auto channel = std::shared_ptr<Channel>(weak.lock())) {
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
if (auto channel = std::shared_ptr<Channel>(weak.lock()))
|
||||
{
|
||||
this->readConnection_->sendRaw("JOIN #" + channel->getName());
|
||||
}
|
||||
}
|
||||
@@ -117,9 +123,12 @@ void AbstractIrcServer::sendRawMessage(const QString &rawMessage)
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(this->connectionMutex_);
|
||||
|
||||
if (this->hasSeparateWriteConnection()) {
|
||||
if (this->hasSeparateWriteConnection())
|
||||
{
|
||||
this->writeConnection_->sendRaw(rawMessage);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
this->readConnection_->sendRaw(rawMessage);
|
||||
}
|
||||
}
|
||||
@@ -136,7 +145,8 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
|
||||
|
||||
// try get channel
|
||||
ChannelPtr chan = this->getChannelOrEmpty(channelName);
|
||||
if (chan != Channel::getEmpty()) {
|
||||
if (chan != Channel::getEmpty())
|
||||
{
|
||||
return chan;
|
||||
}
|
||||
|
||||
@@ -144,7 +154,8 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
|
||||
|
||||
// value doesn't exist
|
||||
chan = this->createChannel(channelName);
|
||||
if (!chan) {
|
||||
if (!chan)
|
||||
{
|
||||
return Channel::getEmpty();
|
||||
}
|
||||
|
||||
@@ -158,11 +169,13 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
|
||||
clojuresInCppAreShit);
|
||||
this->channels.remove(clojuresInCppAreShit);
|
||||
|
||||
if (this->readConnection_) {
|
||||
if (this->readConnection_)
|
||||
{
|
||||
this->readConnection_->sendRaw("PART #" + clojuresInCppAreShit);
|
||||
}
|
||||
|
||||
if (this->writeConnection_) {
|
||||
if (this->writeConnection_)
|
||||
{
|
||||
this->writeConnection_->sendRaw("PART #" + clojuresInCppAreShit);
|
||||
}
|
||||
});
|
||||
@@ -171,11 +184,13 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
|
||||
{
|
||||
std::lock_guard<std::mutex> lock2(this->connectionMutex_);
|
||||
|
||||
if (this->readConnection_) {
|
||||
if (this->readConnection_)
|
||||
{
|
||||
this->readConnection_->sendRaw("JOIN #" + channelName);
|
||||
}
|
||||
|
||||
if (this->writeConnection_) {
|
||||
if (this->writeConnection_)
|
||||
{
|
||||
this->writeConnection_->sendRaw("JOIN #" + channelName);
|
||||
}
|
||||
}
|
||||
@@ -192,16 +207,19 @@ std::shared_ptr<Channel> AbstractIrcServer::getChannelOrEmpty(
|
||||
|
||||
// try get special channel
|
||||
ChannelPtr chan = this->getCustomChannel(channelName);
|
||||
if (chan) {
|
||||
if (chan)
|
||||
{
|
||||
return chan;
|
||||
}
|
||||
|
||||
// value exists
|
||||
auto it = this->channels.find(channelName);
|
||||
if (it != this->channels.end()) {
|
||||
if (it != this->channels.end())
|
||||
{
|
||||
chan = it.value().lock();
|
||||
|
||||
if (chan) {
|
||||
if (chan)
|
||||
{
|
||||
return chan;
|
||||
}
|
||||
}
|
||||
@@ -216,9 +234,11 @@ void AbstractIrcServer::onConnected()
|
||||
auto connected = makeSystemMessage("connected to chat");
|
||||
auto reconnected = makeSystemMessage("reconnected to chat");
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values()) {
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
std::shared_ptr<Channel> chan = weak.lock();
|
||||
if (!chan) {
|
||||
if (!chan)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -228,7 +248,8 @@ void AbstractIrcServer::onConnected()
|
||||
snapshot[snapshot.getLength() - 1]->flags.has(
|
||||
MessageFlag::DisconnectedMessage);
|
||||
|
||||
if (replaceMessage) {
|
||||
if (replaceMessage)
|
||||
{
|
||||
chan->replaceMessage(snapshot[snapshot.getLength() - 1],
|
||||
reconnected);
|
||||
continue;
|
||||
@@ -248,9 +269,11 @@ void AbstractIrcServer::onDisconnected()
|
||||
b->flags.set(MessageFlag::DisconnectedMessage);
|
||||
auto disconnected = b.release();
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values()) {
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
std::shared_ptr<Channel> chan = weak.lock();
|
||||
if (!chan) {
|
||||
if (!chan)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -279,10 +302,13 @@ void AbstractIrcServer::addFakeMessage(const QString &data)
|
||||
auto fakeMessage = Communi::IrcMessage::fromData(
|
||||
data.toUtf8(), this->readConnection_.get());
|
||||
|
||||
if (fakeMessage->command() == "PRIVMSG") {
|
||||
if (fakeMessage->command() == "PRIVMSG")
|
||||
{
|
||||
this->privateMessageReceived(
|
||||
static_cast<Communi::IrcPrivateMessage *>(fakeMessage));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
this->messageReceived(fakeMessage);
|
||||
}
|
||||
}
|
||||
@@ -300,9 +326,11 @@ void AbstractIrcServer::forEachChannel(std::function<void(ChannelPtr)> func)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values()) {
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
std::shared_ptr<Channel> chan = weak.lock();
|
||||
if (!chan) {
|
||||
if (!chan)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@ IrcConnection::IrcConnection(QObject *parent)
|
||||
this->pingTimer_.setInterval(5000);
|
||||
this->pingTimer_.start();
|
||||
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
|
||||
if (this->isConnected()) {
|
||||
if (!this->recentlyReceivedMessage_.load()) {
|
||||
if (this->isConnected())
|
||||
{
|
||||
if (!this->recentlyReceivedMessage_.load())
|
||||
{
|
||||
this->sendRaw("PING");
|
||||
this->reconnectTimer_.start();
|
||||
}
|
||||
@@ -22,7 +24,8 @@ IrcConnection::IrcConnection(QObject *parent)
|
||||
this->reconnectTimer_.setInterval(5000);
|
||||
this->reconnectTimer_.setSingleShot(true);
|
||||
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
|
||||
if (this->isConnected()) {
|
||||
if (this->isConnected())
|
||||
{
|
||||
reconnectRequested.invoke();
|
||||
}
|
||||
});
|
||||
@@ -31,7 +34,8 @@ IrcConnection::IrcConnection(QObject *parent)
|
||||
[this](Communi::IrcMessage *) {
|
||||
this->recentlyReceivedMessage_ = true;
|
||||
|
||||
if (this->reconnectTimer_.isActive()) {
|
||||
if (this->reconnectTimer_.isActive())
|
||||
{
|
||||
this->reconnectTimer_.stop();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -39,29 +39,35 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
|
||||
bool isSub, bool isAction)
|
||||
{
|
||||
QString channelName;
|
||||
if (!trimChannelName(target, channelName)) {
|
||||
if (!trimChannelName(target, channelName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto chan = server.getChannelOrEmpty(channelName);
|
||||
|
||||
if (chan->isEmpty()) {
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MessageParseArgs args;
|
||||
if (isSub) {
|
||||
if (isSub)
|
||||
{
|
||||
args.trimSubscriberUsername = true;
|
||||
}
|
||||
|
||||
if (chan->isBroadcaster()) {
|
||||
if (chan->isBroadcaster())
|
||||
{
|
||||
args.isStaffOrBroadcaster = true;
|
||||
}
|
||||
|
||||
TwitchMessageBuilder builder(chan.get(), _message, args, content, isAction);
|
||||
|
||||
if (isSub || !builder.isIgnored()) {
|
||||
if (isSub) {
|
||||
if (isSub || !builder.isIgnored())
|
||||
{
|
||||
if (isSub)
|
||||
{
|
||||
builder->flags.set(MessageFlag::Subscription);
|
||||
builder->flags.unset(MessageFlag::Highlighted);
|
||||
}
|
||||
@@ -69,8 +75,10 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
|
||||
auto msg = builder.build();
|
||||
auto highlighted = msg->flags.has(MessageFlag::Highlighted);
|
||||
|
||||
if (!isSub) {
|
||||
if (highlighted) {
|
||||
if (!isSub)
|
||||
{
|
||||
if (highlighted)
|
||||
{
|
||||
server.mentionsChannel->addMessage(msg);
|
||||
getApp()->highlights->addHighlight(msg);
|
||||
}
|
||||
@@ -87,16 +95,19 @@ void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message)
|
||||
|
||||
// get twitch channel
|
||||
QString chanName;
|
||||
if (!trimChannelName(message->parameter(0), chanName)) {
|
||||
if (!trimChannelName(message->parameter(0), chanName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto chan = app->twitch.server->getChannelOrEmpty(chanName);
|
||||
|
||||
if (auto *twitchChannel = dynamic_cast<TwitchChannel *>(chan.get())) {
|
||||
if (auto *twitchChannel = dynamic_cast<TwitchChannel *>(chan.get()))
|
||||
{
|
||||
// room-id
|
||||
decltype(tags.find("xD")) it;
|
||||
|
||||
if ((it = tags.find("room-id")) != tags.end()) {
|
||||
if ((it = tags.find("room-id")) != tags.end())
|
||||
{
|
||||
auto roomId = it.value().toString();
|
||||
|
||||
twitchChannel->setRoomId(roomId);
|
||||
@@ -106,19 +117,24 @@ void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message)
|
||||
{
|
||||
auto roomModes = *twitchChannel->accessRoomModes();
|
||||
|
||||
if ((it = tags.find("emote-only")) != tags.end()) {
|
||||
if ((it = tags.find("emote-only")) != tags.end())
|
||||
{
|
||||
roomModes.emoteOnly = it.value() == "1";
|
||||
}
|
||||
if ((it = tags.find("subs-only")) != tags.end()) {
|
||||
if ((it = tags.find("subs-only")) != tags.end())
|
||||
{
|
||||
roomModes.submode = it.value() == "1";
|
||||
}
|
||||
if ((it = tags.find("slow")) != tags.end()) {
|
||||
if ((it = tags.find("slow")) != tags.end())
|
||||
{
|
||||
roomModes.slowMode = it.value().toInt();
|
||||
}
|
||||
if ((it = tags.find("r9k")) != tags.end()) {
|
||||
if ((it = tags.find("r9k")) != tags.end())
|
||||
{
|
||||
roomModes.r9k = it.value() == "1";
|
||||
}
|
||||
if ((it = tags.find("broadcaster-lang")) != tags.end()) {
|
||||
if ((it = tags.find("broadcaster-lang")) != tags.end())
|
||||
{
|
||||
roomModes.broadcasterLang = it.value().toString();
|
||||
}
|
||||
twitchChannel->setRoomModes(roomModes);
|
||||
@@ -131,12 +147,14 @@ void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message)
|
||||
void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
|
||||
{
|
||||
// check parameter count
|
||||
if (message->parameters().length() < 1) {
|
||||
if (message->parameters().length() < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QString chanName;
|
||||
if (!trimChannelName(message->parameter(0), chanName)) {
|
||||
if (!trimChannelName(message->parameter(0), chanName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -145,7 +163,8 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
|
||||
// get channel
|
||||
auto chan = app->twitch.server->getChannelOrEmpty(chanName);
|
||||
|
||||
if (chan->isEmpty()) {
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
|
||||
"found",
|
||||
chanName);
|
||||
@@ -153,7 +172,8 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
|
||||
}
|
||||
|
||||
// check if the chat has been cleared by a moderator
|
||||
if (message->parameters().length() == 1) {
|
||||
if (message->parameters().length() == 1)
|
||||
{
|
||||
chan->disableAllMessages();
|
||||
chan->addMessage(
|
||||
makeSystemMessage("Chat has been cleared by a moderator."));
|
||||
@@ -165,12 +185,14 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
|
||||
QString username = message->parameter(1);
|
||||
QString durationInSeconds, reason;
|
||||
QVariant v = message->tag("ban-duration");
|
||||
if (v.isValid()) {
|
||||
if (v.isValid())
|
||||
{
|
||||
durationInSeconds = v.toString();
|
||||
}
|
||||
|
||||
v = message->tag("ban-reason");
|
||||
if (v.isValid()) {
|
||||
if (v.isValid())
|
||||
{
|
||||
reason = v.toString();
|
||||
}
|
||||
|
||||
@@ -187,21 +209,25 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
|
||||
{
|
||||
QVariant _mod = message->tag("mod");
|
||||
|
||||
if (_mod.isValid()) {
|
||||
if (_mod.isValid())
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
QString channelName;
|
||||
if (!trimChannelName(message->parameter(0), channelName)) {
|
||||
if (!trimChannelName(message->parameter(0), channelName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto c = app->twitch.server->getChannelOrEmpty(channelName);
|
||||
if (c->isEmpty()) {
|
||||
if (c->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TwitchChannel *tc = dynamic_cast<TwitchChannel *>(c.get());
|
||||
if (tc != nullptr) {
|
||||
if (tc != nullptr)
|
||||
{
|
||||
tc->setMod(_mod == "1");
|
||||
}
|
||||
}
|
||||
@@ -220,12 +246,14 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
|
||||
TwitchMessageBuilder builder(c, message, args, message->parameter(1),
|
||||
false);
|
||||
|
||||
if (!builder.isIgnored()) {
|
||||
if (!builder.isIgnored())
|
||||
{
|
||||
MessagePtr _message = builder.build();
|
||||
|
||||
app->twitch.server->lastUserThatWhisperedMe.set(builder.userName);
|
||||
|
||||
if (_message->flags.has(MessageFlag::Highlighted)) {
|
||||
if (_message->flags.has(MessageFlag::Highlighted))
|
||||
{
|
||||
app->twitch.server->mentionsChannel->addMessage(_message);
|
||||
}
|
||||
|
||||
@@ -234,7 +262,8 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
|
||||
auto overrideFlags = boost::optional<MessageFlags>(_message->flags);
|
||||
overrideFlags->set(MessageFlag::DoNotTriggerNotification);
|
||||
|
||||
if (getSettings()->inlineWhispers) {
|
||||
if (getSettings()->inlineWhispers)
|
||||
{
|
||||
app->twitch.server->forEachChannel(
|
||||
[_message, overrideFlags](ChannelPtr channel) {
|
||||
channel->addMessage(_message, overrideFlags); //
|
||||
@@ -254,21 +283,25 @@ void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message,
|
||||
auto target = parameters[0];
|
||||
QString msgType = tags.value("msg-id", "").toString();
|
||||
QString content;
|
||||
if (parameters.size() >= 2) {
|
||||
if (parameters.size() >= 2)
|
||||
{
|
||||
content = parameters[1];
|
||||
}
|
||||
|
||||
if (msgType == "sub" || msgType == "resub" || msgType == "subgift") {
|
||||
if (msgType == "sub" || msgType == "resub" || msgType == "subgift")
|
||||
{
|
||||
// Sub-specific message. I think it's only allowed for "resub" messages
|
||||
// atm
|
||||
if (!content.isEmpty()) {
|
||||
if (!content.isEmpty())
|
||||
{
|
||||
this->addMessage(message, target, content, server, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
auto it = tags.find("system-msg");
|
||||
|
||||
if (it != tags.end()) {
|
||||
if (it != tags.end())
|
||||
{
|
||||
auto b = MessageBuilder(systemMessage,
|
||||
parseTagString(it.value().toString()));
|
||||
|
||||
@@ -277,17 +310,20 @@ void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message,
|
||||
|
||||
QString channelName;
|
||||
|
||||
if (message->parameters().size() < 1) {
|
||||
if (message->parameters().size() < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!trimChannelName(message->parameter(0), channelName)) {
|
||||
if (!trimChannelName(message->parameter(0), channelName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto chan = server.getChannelOrEmpty(channelName);
|
||||
|
||||
if (!chan->isEmpty()) {
|
||||
if (!chan->isEmpty())
|
||||
{
|
||||
chan->addMessage(newMessage);
|
||||
}
|
||||
}
|
||||
@@ -300,13 +336,17 @@ void IrcMessageHandler::handleModeMessage(Communi::IrcMessage *message)
|
||||
auto channel = app->twitch.server->getChannelOrEmpty(
|
||||
message->parameter(0).remove(0, 1));
|
||||
|
||||
if (channel->isEmpty()) {
|
||||
if (channel->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (message->parameter(1) == "+o") {
|
||||
if (message->parameter(1) == "+o")
|
||||
{
|
||||
channel->modList.append(message->parameter(2));
|
||||
} else if (message->parameter(1) == "-o") {
|
||||
}
|
||||
else if (message->parameter(1) == "-o")
|
||||
{
|
||||
channel->modList.append(message->parameter(2));
|
||||
}
|
||||
}
|
||||
@@ -317,7 +357,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
|
||||
MessagePtr msg = makeSystemMessage(message->content());
|
||||
|
||||
QString channelName;
|
||||
if (!trimChannelName(message->target(), channelName)) {
|
||||
if (!trimChannelName(message->target(), channelName))
|
||||
{
|
||||
// Notice wasn't targeted at a single channel, send to all twitch
|
||||
// channels
|
||||
app->twitch.server->forEachChannelAndSpecialChannels(
|
||||
@@ -330,7 +371,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
|
||||
|
||||
auto channel = app->twitch.server->getChannelOrEmpty(channelName);
|
||||
|
||||
if (channel->isEmpty()) {
|
||||
if (channel->isEmpty())
|
||||
{
|
||||
log("[IrcManager:handleNoticeMessage] Channel {} not found in channel "
|
||||
"manager ",
|
||||
channelName);
|
||||
@@ -367,10 +409,12 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(
|
||||
};
|
||||
|
||||
QVariant v = message->tag("msg-id");
|
||||
if (v.isValid()) {
|
||||
if (v.isValid())
|
||||
{
|
||||
std::string msgID = v.toString().toStdString();
|
||||
|
||||
if (readConnectionOnlyIDs.find(msgID) != readConnectionOnlyIDs.end()) {
|
||||
if (readConnectionOnlyIDs.find(msgID) != readConnectionOnlyIDs.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -388,7 +432,8 @@ void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
|
||||
message->parameter(0).remove(0, 1));
|
||||
|
||||
if (TwitchChannel *twitchChannel =
|
||||
dynamic_cast<TwitchChannel *>(channel.get())) {
|
||||
dynamic_cast<TwitchChannel *>(channel.get()))
|
||||
{
|
||||
twitchChannel->addJoinedUser(message->nick());
|
||||
}
|
||||
}
|
||||
@@ -400,7 +445,8 @@ void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message)
|
||||
message->parameter(0).remove(0, 1));
|
||||
|
||||
if (TwitchChannel *twitchChannel =
|
||||
dynamic_cast<TwitchChannel *>(channel.get())) {
|
||||
dynamic_cast<TwitchChannel *>(channel.get()))
|
||||
{
|
||||
twitchChannel->addPartedUser(message->nick());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
|
||||
{
|
||||
assert(!this->username_.isEmpty());
|
||||
|
||||
if (caller == nullptr) {
|
||||
if (caller == nullptr)
|
||||
{
|
||||
caller = QThread::currentThread();
|
||||
}
|
||||
|
||||
@@ -42,23 +43,27 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
|
||||
|
||||
request.onSuccess([successCallback](auto result) -> Outcome {
|
||||
auto root = result.parseJson();
|
||||
if (!root.value("users").isArray()) {
|
||||
if (!root.value("users").isArray())
|
||||
{
|
||||
log("API Error while getting user id, users is not an array");
|
||||
return Failure;
|
||||
}
|
||||
|
||||
auto users = root.value("users").toArray();
|
||||
if (users.size() != 1) {
|
||||
if (users.size() != 1)
|
||||
{
|
||||
log("API Error while getting user id, users array size is not 1");
|
||||
return Failure;
|
||||
}
|
||||
if (!users[0].isObject()) {
|
||||
if (!users[0].isObject())
|
||||
{
|
||||
log("API Error while getting user id, first user is not an object");
|
||||
return Failure;
|
||||
}
|
||||
auto firstUser = users[0].toObject();
|
||||
auto id = firstUser.value("_id");
|
||||
if (!id.isString()) {
|
||||
if (!id.isString())
|
||||
{
|
||||
log("API Error: while getting user id, first user object `_id` key "
|
||||
"is not a "
|
||||
"string");
|
||||
|
||||
@@ -44,7 +44,8 @@ struct ModeChangedAction : PubSubAction {
|
||||
|
||||
const char *getModeName() const
|
||||
{
|
||||
switch (this->mode) {
|
||||
switch (this->mode)
|
||||
{
|
||||
case Mode::Slow:
|
||||
return "slow";
|
||||
case Mode::R9K:
|
||||
|
||||
@@ -51,14 +51,16 @@ namespace detail {
|
||||
{
|
||||
int numRequestedListens = message["data"]["topics"].Size();
|
||||
|
||||
if (this->numListens_ + numRequestedListens > MAX_PUBSUB_LISTENS) {
|
||||
if (this->numListens_ + numRequestedListens > MAX_PUBSUB_LISTENS)
|
||||
{
|
||||
// This PubSubClient is already at its peak listens
|
||||
return false;
|
||||
}
|
||||
|
||||
this->numListens_ += numRequestedListens;
|
||||
|
||||
for (const auto &topic : message["data"]["topics"].GetArray()) {
|
||||
for (const auto &topic : message["data"]["topics"].GetArray())
|
||||
{
|
||||
this->listeners_.emplace_back(
|
||||
Listener{topic.GetString(), false, false, false});
|
||||
}
|
||||
@@ -79,18 +81,22 @@ namespace detail {
|
||||
{
|
||||
std::vector<std::string> topics;
|
||||
|
||||
for (auto it = this->listeners_.begin();
|
||||
it != this->listeners_.end();) {
|
||||
for (auto it = this->listeners_.begin(); it != this->listeners_.end();)
|
||||
{
|
||||
const auto &listener = *it;
|
||||
if (listener.topic.find(prefix) == 0) {
|
||||
if (listener.topic.find(prefix) == 0)
|
||||
{
|
||||
topics.push_back(listener.topic);
|
||||
it = this->listeners_.erase(it);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (topics.empty()) {
|
||||
if (topics.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -117,8 +123,10 @@ namespace detail {
|
||||
|
||||
bool PubSubClient::isListeningToTopic(const std::string &payload)
|
||||
{
|
||||
for (const auto &listener : this->listeners_) {
|
||||
if (listener.topic == payload) {
|
||||
for (const auto &listener : this->listeners_)
|
||||
{
|
||||
if (listener.topic == payload)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -130,7 +138,8 @@ namespace detail {
|
||||
{
|
||||
assert(this->started_);
|
||||
|
||||
if (!this->send(pingPayload)) {
|
||||
if (!this->send(pingPayload))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -140,11 +149,13 @@ namespace detail {
|
||||
|
||||
runAfter(this->websocketClient_.get_io_service(),
|
||||
std::chrono::seconds(15), [self](auto timer) {
|
||||
if (!self->started_) {
|
||||
if (!self->started_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->awaitingPong_) {
|
||||
if (self->awaitingPong_)
|
||||
{
|
||||
log("No pong respnose, disconnect!");
|
||||
// TODO(pajlada): Label this connection as "disconnect
|
||||
// me"
|
||||
@@ -153,7 +164,8 @@ namespace detail {
|
||||
|
||||
runAfter(this->websocketClient_.get_io_service(),
|
||||
std::chrono::minutes(5), [self](auto timer) {
|
||||
if (!self->started_) {
|
||||
if (!self->started_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -167,7 +179,8 @@ namespace detail {
|
||||
this->websocketClient_.send(this->handle_, payload,
|
||||
websocketpp::frame::opcode::text, ec);
|
||||
|
||||
if (ec) {
|
||||
if (ec)
|
||||
{
|
||||
log("Error sending message {}: {}", payload, ec.message());
|
||||
// TODO(pajlada): Check which error code happened and maybe
|
||||
// gracefully handle it
|
||||
@@ -208,26 +221,30 @@ PubSub::PubSub()
|
||||
action.mode = ModeChangedAction::Mode::Slow;
|
||||
action.state = ModeChangedAction::State::On;
|
||||
|
||||
if (!data.HasMember("args")) {
|
||||
if (!data.HasMember("args"))
|
||||
{
|
||||
log("Missing required args member");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &args = data["args"];
|
||||
|
||||
if (!args.IsArray()) {
|
||||
if (!args.IsArray())
|
||||
{
|
||||
log("args member must be an array");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Size() == 0) {
|
||||
if (args.Size() == 0)
|
||||
{
|
||||
log("Missing duration argument in slowmode on");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &durationArg = args[0];
|
||||
|
||||
if (!durationArg.IsString()) {
|
||||
if (!durationArg.IsString())
|
||||
{
|
||||
log("Duration arg must be a string");
|
||||
return;
|
||||
}
|
||||
@@ -305,17 +322,22 @@ PubSub::PubSub()
|
||||
|
||||
getTargetUser(data, action.target);
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
const auto &args = getArgs(data);
|
||||
|
||||
if (args.Size() < 1) {
|
||||
if (args.Size() < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rj::getSafe(args[0], action.target.name)) {
|
||||
if (!rj::getSafe(args[0], action.target.name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
} catch (const std::runtime_error &ex) {
|
||||
}
|
||||
catch (const std::runtime_error &ex)
|
||||
{
|
||||
log("Error parsing moderation action: {}", ex.what());
|
||||
}
|
||||
|
||||
@@ -330,17 +352,22 @@ PubSub::PubSub()
|
||||
|
||||
getTargetUser(data, action.target);
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
const auto &args = getArgs(data);
|
||||
|
||||
if (args.Size() < 1) {
|
||||
if (args.Size() < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rj::getSafe(args[0], action.target.name)) {
|
||||
if (!rj::getSafe(args[0], action.target.name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
} catch (const std::runtime_error &ex) {
|
||||
}
|
||||
catch (const std::runtime_error &ex)
|
||||
{
|
||||
log("Error parsing moderation action: {}", ex.what());
|
||||
}
|
||||
|
||||
@@ -356,32 +383,40 @@ PubSub::PubSub()
|
||||
getCreatedByUser(data, action.source);
|
||||
getTargetUser(data, action.target);
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
const auto &args = getArgs(data);
|
||||
|
||||
if (args.Size() < 2) {
|
||||
if (args.Size() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rj::getSafe(args[0], action.target.name)) {
|
||||
if (!rj::getSafe(args[0], action.target.name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QString durationString;
|
||||
if (!rj::getSafe(args[1], durationString)) {
|
||||
if (!rj::getSafe(args[1], durationString))
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool ok;
|
||||
action.duration = durationString.toUInt(&ok, 10);
|
||||
|
||||
if (args.Size() >= 3) {
|
||||
if (!rj::getSafe(args[2], action.reason)) {
|
||||
if (args.Size() >= 3)
|
||||
{
|
||||
if (!rj::getSafe(args[2], action.reason))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->signals_.moderation.userBanned.invoke(action);
|
||||
} catch (const std::runtime_error &ex) {
|
||||
}
|
||||
catch (const std::runtime_error &ex)
|
||||
{
|
||||
log("Error parsing moderation action: {}", ex.what());
|
||||
}
|
||||
};
|
||||
@@ -393,25 +428,32 @@ PubSub::PubSub()
|
||||
getCreatedByUser(data, action.source);
|
||||
getTargetUser(data, action.target);
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
const auto &args = getArgs(data);
|
||||
|
||||
if (args.Size() < 1) {
|
||||
if (args.Size() < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rj::getSafe(args[0], action.target.name)) {
|
||||
if (!rj::getSafe(args[0], action.target.name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Size() >= 2) {
|
||||
if (!rj::getSafe(args[1], action.reason)) {
|
||||
if (args.Size() >= 2)
|
||||
{
|
||||
if (!rj::getSafe(args[1], action.reason))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->signals_.moderation.userBanned.invoke(action);
|
||||
} catch (const std::runtime_error &ex) {
|
||||
}
|
||||
catch (const std::runtime_error &ex)
|
||||
{
|
||||
log("Error parsing moderation action: {}", ex.what());
|
||||
}
|
||||
};
|
||||
@@ -425,19 +467,24 @@ PubSub::PubSub()
|
||||
|
||||
action.previousState = UnbanAction::Banned;
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
const auto &args = getArgs(data);
|
||||
|
||||
if (args.Size() < 1) {
|
||||
if (args.Size() < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rj::getSafe(args[0], action.target.name)) {
|
||||
if (!rj::getSafe(args[0], action.target.name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->signals_.moderation.userUnbanned.invoke(action);
|
||||
} catch (const std::runtime_error &ex) {
|
||||
}
|
||||
catch (const std::runtime_error &ex)
|
||||
{
|
||||
log("Error parsing moderation action: {}", ex.what());
|
||||
}
|
||||
};
|
||||
@@ -451,19 +498,24 @@ PubSub::PubSub()
|
||||
|
||||
action.previousState = UnbanAction::TimedOut;
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
const auto &args = getArgs(data);
|
||||
|
||||
if (args.Size() < 1) {
|
||||
if (args.Size() < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rj::getSafe(args[0], action.target.name)) {
|
||||
if (!rj::getSafe(args[0], action.target.name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->signals_.moderation.userUnbanned.invoke(action);
|
||||
} catch (const std::runtime_error &ex) {
|
||||
}
|
||||
catch (const std::runtime_error &ex)
|
||||
{
|
||||
log("Error parsing moderation action: {}", ex.what());
|
||||
}
|
||||
};
|
||||
@@ -494,7 +546,8 @@ void PubSub::addClient()
|
||||
websocketpp::lib::error_code ec;
|
||||
auto con = this->websocketClient.get_connection(TWITCH_PUBSUB_URL, ec);
|
||||
|
||||
if (ec) {
|
||||
if (ec)
|
||||
{
|
||||
log("Unable to establish connection: {}", ec.message());
|
||||
return;
|
||||
}
|
||||
@@ -521,7 +574,8 @@ void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
|
||||
|
||||
this->listen(createListenMessage(topics, account));
|
||||
|
||||
if (ec) {
|
||||
if (ec)
|
||||
{
|
||||
log("Unable to send message to websocket server: {}", ec.message());
|
||||
return;
|
||||
}
|
||||
@@ -529,7 +583,8 @@ void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
|
||||
|
||||
void PubSub::unlistenAllModerationActions()
|
||||
{
|
||||
for (const auto &p : this->clients) {
|
||||
for (const auto &p : this->clients)
|
||||
{
|
||||
const auto &client = p.second;
|
||||
client->unlistenPrefix("chat_moderator_actions.");
|
||||
}
|
||||
@@ -541,11 +596,13 @@ void PubSub::listenToChannelModerationActions(
|
||||
assert(!channelID.isEmpty());
|
||||
assert(account != nullptr);
|
||||
QString userID = account->getUserId();
|
||||
if (userID.isEmpty()) return;
|
||||
if (userID.isEmpty())
|
||||
return;
|
||||
|
||||
std::string topic(fS("chat_moderator_actions.{}.{}", userID, channelID));
|
||||
|
||||
if (this->isListeningToTopic(topic)) {
|
||||
if (this->isListeningToTopic(topic))
|
||||
{
|
||||
log("We are already listening to topic {}", topic);
|
||||
return;
|
||||
}
|
||||
@@ -565,7 +622,8 @@ void PubSub::listenToTopic(const std::string &topic,
|
||||
|
||||
void PubSub::listen(rapidjson::Document &&msg)
|
||||
{
|
||||
if (this->tryListen(msg)) {
|
||||
if (this->tryListen(msg))
|
||||
{
|
||||
log("Successfully listened!");
|
||||
return;
|
||||
}
|
||||
@@ -578,9 +636,11 @@ void PubSub::listen(rapidjson::Document &&msg)
|
||||
bool PubSub::tryListen(rapidjson::Document &msg)
|
||||
{
|
||||
log("tryListen with {} clients", this->clients.size());
|
||||
for (const auto &p : this->clients) {
|
||||
for (const auto &p : this->clients)
|
||||
{
|
||||
const auto &client = p.second;
|
||||
if (client->listen(msg)) {
|
||||
if (client->listen(msg))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -590,9 +650,11 @@ bool PubSub::tryListen(rapidjson::Document &msg)
|
||||
|
||||
bool PubSub::isListeningToTopic(const std::string &topic)
|
||||
{
|
||||
for (const auto &p : this->clients) {
|
||||
for (const auto &p : this->clients)
|
||||
{
|
||||
const auto &client = p.second;
|
||||
if (client->isListeningToTopic(topic)) {
|
||||
if (client->isListeningToTopic(topic))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -609,13 +671,15 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
||||
|
||||
rapidjson::ParseResult res = msg.Parse(payload.c_str());
|
||||
|
||||
if (!res) {
|
||||
if (!res)
|
||||
{
|
||||
log("Error parsing message '{}' from PubSub: {}", payload,
|
||||
rapidjson::GetParseError_En(res.Code()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!msg.IsObject()) {
|
||||
if (!msg.IsObject())
|
||||
{
|
||||
log("Error parsing message '{}' from PubSub. Root object is not an "
|
||||
"object",
|
||||
payload);
|
||||
@@ -624,28 +688,36 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
||||
|
||||
std::string type;
|
||||
|
||||
if (!rj::getSafe(msg, "type", type)) {
|
||||
if (!rj::getSafe(msg, "type", type))
|
||||
{
|
||||
log("Missing required string member `type` in message root");
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "RESPONSE") {
|
||||
if (type == "RESPONSE")
|
||||
{
|
||||
this->handleListenResponse(msg);
|
||||
} else if (type == "MESSAGE") {
|
||||
if (!msg.HasMember("data")) {
|
||||
}
|
||||
else if (type == "MESSAGE")
|
||||
{
|
||||
if (!msg.HasMember("data"))
|
||||
{
|
||||
log("Missing required object member `data` in message root");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &data = msg["data"];
|
||||
|
||||
if (!data.IsObject()) {
|
||||
if (!data.IsObject())
|
||||
{
|
||||
log("Member `data` must be an object");
|
||||
return;
|
||||
}
|
||||
|
||||
this->handleMessageResponse(data);
|
||||
} else if (type == "PONG") {
|
||||
}
|
||||
else if (type == "PONG")
|
||||
{
|
||||
auto clientIt = this->clients.find(hdl);
|
||||
|
||||
// If this assert goes off, there's something wrong with the connection
|
||||
@@ -655,7 +727,9 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
||||
auto &client = *clientIt;
|
||||
|
||||
client.second->handlePong();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
log("Unknown message type: {}", type);
|
||||
}
|
||||
}
|
||||
@@ -696,11 +770,14 @@ PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl)
|
||||
WebsocketContextPtr ctx(
|
||||
new boost::asio::ssl::context(boost::asio::ssl::context::tlsv1));
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
ctx->set_options(boost::asio::ssl::context::default_workarounds |
|
||||
boost::asio::ssl::context::no_sslv2 |
|
||||
boost::asio::ssl::context::single_dh_use);
|
||||
} catch (const std::exception &e) {
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
log("Exception caught in OnTLSInit: {}", e.what());
|
||||
}
|
||||
|
||||
@@ -711,11 +788,13 @@ void PubSub::handleListenResponse(const rapidjson::Document &msg)
|
||||
{
|
||||
std::string error;
|
||||
|
||||
if (rj::getSafe(msg, "error", error)) {
|
||||
if (rj::getSafe(msg, "error", error))
|
||||
{
|
||||
std::string nonce;
|
||||
rj::getSafe(msg, "nonce", nonce);
|
||||
|
||||
if (error.empty()) {
|
||||
if (error.empty())
|
||||
{
|
||||
log("Successfully listened to nonce {}", nonce);
|
||||
// Nothing went wrong
|
||||
return;
|
||||
@@ -730,14 +809,16 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
||||
{
|
||||
QString topic;
|
||||
|
||||
if (!rj::getSafe(outerData, "topic", topic)) {
|
||||
if (!rj::getSafe(outerData, "topic", topic))
|
||||
{
|
||||
log("Missing required string member `topic` in outerData");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string payload;
|
||||
|
||||
if (!rj::getSafe(outerData, "message", payload)) {
|
||||
if (!rj::getSafe(outerData, "message", payload))
|
||||
{
|
||||
log("Expected string message in outerData");
|
||||
return;
|
||||
}
|
||||
@@ -746,53 +827,69 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
||||
|
||||
rapidjson::ParseResult res = msg.Parse(payload.c_str());
|
||||
|
||||
if (!res) {
|
||||
if (!res)
|
||||
{
|
||||
log("Error parsing message '{}' from PubSub: {}", payload,
|
||||
rapidjson::GetParseError_En(res.Code()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (topic.startsWith("whispers.")) {
|
||||
if (topic.startsWith("whispers."))
|
||||
{
|
||||
std::string whisperType;
|
||||
|
||||
if (!rj::getSafe(msg, "type", whisperType)) {
|
||||
if (!rj::getSafe(msg, "type", whisperType))
|
||||
{
|
||||
log("Bad whisper data");
|
||||
return;
|
||||
}
|
||||
|
||||
if (whisperType == "whisper_received") {
|
||||
if (whisperType == "whisper_received")
|
||||
{
|
||||
this->signals_.whisper.received.invoke(msg);
|
||||
} else if (whisperType == "whisper_sent") {
|
||||
}
|
||||
else if (whisperType == "whisper_sent")
|
||||
{
|
||||
this->signals_.whisper.sent.invoke(msg);
|
||||
} else if (whisperType == "thread") {
|
||||
}
|
||||
else if (whisperType == "thread")
|
||||
{
|
||||
// Handle thread?
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
log("Invalid whisper type: {}", whisperType);
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
} else if (topic.startsWith("chat_moderator_actions.")) {
|
||||
}
|
||||
else if (topic.startsWith("chat_moderator_actions."))
|
||||
{
|
||||
auto topicParts = topic.split(".");
|
||||
assert(topicParts.length() == 3);
|
||||
const auto &data = msg["data"];
|
||||
|
||||
std::string moderationAction;
|
||||
|
||||
if (!rj::getSafe(data, "moderation_action", moderationAction)) {
|
||||
if (!rj::getSafe(data, "moderation_action", moderationAction))
|
||||
{
|
||||
log("Missing moderation action in data: {}", rj::stringify(data));
|
||||
return;
|
||||
}
|
||||
|
||||
auto handlerIt = this->moderationActionHandlers.find(moderationAction);
|
||||
|
||||
if (handlerIt == this->moderationActionHandlers.end()) {
|
||||
if (handlerIt == this->moderationActionHandlers.end())
|
||||
{
|
||||
log("No handler found for moderation action {}", moderationAction);
|
||||
return;
|
||||
}
|
||||
|
||||
// Invoke handler function
|
||||
handlerIt->second(data, topicParts[2]);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
log("Unknown topic: {}", topic);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,13 +8,15 @@ namespace chatterino {
|
||||
|
||||
const rapidjson::Value &getArgs(const rapidjson::Value &data)
|
||||
{
|
||||
if (!data.HasMember("args")) {
|
||||
if (!data.HasMember("args"))
|
||||
{
|
||||
throw std::runtime_error("Missing member args");
|
||||
}
|
||||
|
||||
const auto &args = data["args"];
|
||||
|
||||
if (!args.IsArray()) {
|
||||
if (!args.IsArray())
|
||||
{
|
||||
throw std::runtime_error("args must be an array");
|
||||
}
|
||||
|
||||
@@ -43,12 +45,14 @@ rapidjson::Document createListenMessage(
|
||||
|
||||
rapidjson::Value data(rapidjson::kObjectType);
|
||||
|
||||
if (account) {
|
||||
if (account)
|
||||
{
|
||||
rj::set(data, "auth_token", account->getOAuthToken(), a);
|
||||
}
|
||||
|
||||
rapidjson::Value topics(rapidjson::kArrayType);
|
||||
for (const auto &topic : topicsVec) {
|
||||
for (const auto &topic : topicsVec)
|
||||
{
|
||||
rj::add(topics, topic, a);
|
||||
}
|
||||
|
||||
@@ -70,7 +74,8 @@ rapidjson::Document createUnlistenMessage(
|
||||
rapidjson::Value data(rapidjson::kObjectType);
|
||||
|
||||
rapidjson::Value topics(rapidjson::kArrayType);
|
||||
for (const auto &topic : topicsVec) {
|
||||
for (const auto &topic : topicsVec)
|
||||
{
|
||||
rj::add(topics, topic, a);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ void runAfter(boost::asio::io_service &ioService, Duration duration,
|
||||
timer->expires_from_now(duration);
|
||||
|
||||
timer->async_wait([timer, cb](const boost::system::error_code &ec) {
|
||||
if (ec) {
|
||||
if (ec)
|
||||
{
|
||||
log("Error in runAfter: {}", ec.message());
|
||||
return;
|
||||
}
|
||||
@@ -49,7 +50,8 @@ void runAfter(std::shared_ptr<boost::asio::steady_timer> timer,
|
||||
timer->expires_from_now(duration);
|
||||
|
||||
timer->async_wait([timer, cb](const boost::system::error_code &ec) {
|
||||
if (ec) {
|
||||
if (ec)
|
||||
{
|
||||
log("Error in runAfter: {}", ec.message());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ namespace {
|
||||
};
|
||||
|
||||
auto it = emoteNameReplacements.find(dirtyEmoteCode.string);
|
||||
if (it != emoteNameReplacements.end()) {
|
||||
if (it != emoteNameReplacements.end())
|
||||
{
|
||||
cleanCode = it.value();
|
||||
}
|
||||
|
||||
@@ -91,7 +92,8 @@ void TwitchAccount::setColor(QColor color)
|
||||
|
||||
bool TwitchAccount::setOAuthClient(const QString &newClientID)
|
||||
{
|
||||
if (this->oauthClient_.compare(newClientID) == 0) {
|
||||
if (this->oauthClient_.compare(newClientID) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -102,7 +104,8 @@ bool TwitchAccount::setOAuthClient(const QString &newClientID)
|
||||
|
||||
bool TwitchAccount::setOAuthToken(const QString &newOAuthToken)
|
||||
{
|
||||
if (this->oauthToken_.compare(newOAuthToken) == 0) {
|
||||
if (this->oauthToken_.compare(newOAuthToken) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -126,17 +129,20 @@ void TwitchAccount::loadIgnores()
|
||||
req.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken());
|
||||
req.onSuccess([=](auto result) -> Outcome {
|
||||
auto document = result.parseRapidJson();
|
||||
if (!document.IsObject()) {
|
||||
if (!document.IsObject())
|
||||
{
|
||||
return Failure;
|
||||
}
|
||||
|
||||
auto blocksIt = document.FindMember("blocks");
|
||||
if (blocksIt == document.MemberEnd()) {
|
||||
if (blocksIt == document.MemberEnd())
|
||||
{
|
||||
return Failure;
|
||||
}
|
||||
const auto &blocks = blocksIt->value;
|
||||
|
||||
if (!blocks.IsArray()) {
|
||||
if (!blocks.IsArray())
|
||||
{
|
||||
return Failure;
|
||||
}
|
||||
|
||||
@@ -144,16 +150,20 @@ void TwitchAccount::loadIgnores()
|
||||
std::lock_guard<std::mutex> lock(this->ignoresMutex_);
|
||||
this->ignores_.clear();
|
||||
|
||||
for (const auto &block : blocks.GetArray()) {
|
||||
if (!block.IsObject()) {
|
||||
for (const auto &block : blocks.GetArray())
|
||||
{
|
||||
if (!block.IsObject())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto userIt = block.FindMember("user");
|
||||
if (userIt == block.MemberEnd()) {
|
||||
if (userIt == block.MemberEnd())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
TwitchUser ignoredUser;
|
||||
if (!rj::getSafe(userIt->value, ignoredUser)) {
|
||||
if (!rj::getSafe(userIt->value, ignoredUser))
|
||||
{
|
||||
log("Error parsing twitch user JSON {}",
|
||||
rj::stringify(userIt->value));
|
||||
continue;
|
||||
@@ -201,14 +211,16 @@ void TwitchAccount::ignoreByID(
|
||||
|
||||
req.onSuccess([=](auto result) -> Outcome {
|
||||
auto document = result.parseRapidJson();
|
||||
if (!document.IsObject()) {
|
||||
if (!document.IsObject())
|
||||
{
|
||||
onFinished(IgnoreResult_Failed,
|
||||
"Bad JSON data while ignoring user " + targetName);
|
||||
return Failure;
|
||||
}
|
||||
|
||||
auto userIt = document.FindMember("user");
|
||||
if (userIt == document.MemberEnd()) {
|
||||
if (userIt == document.MemberEnd())
|
||||
{
|
||||
onFinished(IgnoreResult_Failed,
|
||||
"Bad JSON data while ignoring user (missing user) " +
|
||||
targetName);
|
||||
@@ -216,7 +228,8 @@ void TwitchAccount::ignoreByID(
|
||||
}
|
||||
|
||||
TwitchUser ignoredUser;
|
||||
if (!rj::getSafe(userIt->value, ignoredUser)) {
|
||||
if (!rj::getSafe(userIt->value, ignoredUser))
|
||||
{
|
||||
onFinished(IgnoreResult_Failed,
|
||||
"Bad JSON data while ignoring user (invalid user) " +
|
||||
targetName);
|
||||
@@ -226,7 +239,8 @@ void TwitchAccount::ignoreByID(
|
||||
std::lock_guard<std::mutex> lock(this->ignoresMutex_);
|
||||
|
||||
auto res = this->ignores_.insert(ignoredUser);
|
||||
if (!res.second) {
|
||||
if (!res.second)
|
||||
{
|
||||
const TwitchUser &existingUser = *(res.first);
|
||||
existingUser.update(ignoredUser);
|
||||
onFinished(IgnoreResult_AlreadyIgnored,
|
||||
@@ -303,9 +317,12 @@ void TwitchAccount::checkFollow(const QString targetUserID,
|
||||
req.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken());
|
||||
|
||||
req.onError([=](int errorCode) {
|
||||
if (errorCode == 203) {
|
||||
if (errorCode == 203)
|
||||
{
|
||||
onFinished(FollowResult_NotFollowing);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
onFinished(FollowResult_Failed);
|
||||
}
|
||||
|
||||
@@ -354,7 +371,8 @@ void TwitchAccount::unfollowUser(const QString userID,
|
||||
request.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken());
|
||||
|
||||
request.onError([successCallback](int code) {
|
||||
if (code >= 200 && code <= 299) {
|
||||
if (code >= 200 && code <= 299)
|
||||
{
|
||||
successCallback();
|
||||
}
|
||||
|
||||
@@ -384,7 +402,8 @@ void TwitchAccount::loadEmotes()
|
||||
const auto &clientID = this->getOAuthClient();
|
||||
const auto &oauthToken = this->getOAuthToken();
|
||||
|
||||
if (clientID.isEmpty() || oauthToken.isEmpty()) {
|
||||
if (clientID.isEmpty() || oauthToken.isEmpty())
|
||||
{
|
||||
log("Missing Client ID or OAuth token");
|
||||
return;
|
||||
}
|
||||
@@ -398,9 +417,12 @@ void TwitchAccount::loadEmotes()
|
||||
|
||||
req.onError([=](int errorCode) {
|
||||
log("[TwitchAccount::loadEmotes] Error {}", errorCode);
|
||||
if (errorCode == 203) {
|
||||
if (errorCode == 203)
|
||||
{
|
||||
// onFinished(FollowResult_NotFollowing);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// onFinished(FollowResult_Failed);
|
||||
}
|
||||
|
||||
@@ -430,33 +452,38 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
|
||||
emoteData->allEmoteNames.clear();
|
||||
|
||||
auto emoticonSets = root.FindMember("emoticon_sets");
|
||||
if (emoticonSets == root.MemberEnd() || !emoticonSets->value.IsObject()) {
|
||||
if (emoticonSets == root.MemberEnd() || !emoticonSets->value.IsObject())
|
||||
{
|
||||
log("No emoticon_sets in load emotes response");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &emoteSetJSON : emoticonSets->value.GetObject()) {
|
||||
for (const auto &emoteSetJSON : emoticonSets->value.GetObject())
|
||||
{
|
||||
auto emoteSet = std::make_shared<EmoteSet>();
|
||||
|
||||
emoteSet->key = emoteSetJSON.name.GetString();
|
||||
|
||||
this->loadEmoteSetData(emoteSet);
|
||||
|
||||
for (const rapidjson::Value &emoteJSON :
|
||||
emoteSetJSON.value.GetArray()) {
|
||||
if (!emoteJSON.IsObject()) {
|
||||
for (const rapidjson::Value &emoteJSON : emoteSetJSON.value.GetArray())
|
||||
{
|
||||
if (!emoteJSON.IsObject())
|
||||
{
|
||||
log("Emote value was invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t idNumber;
|
||||
if (!rj::getSafe(emoteJSON, "id", idNumber)) {
|
||||
if (!rj::getSafe(emoteJSON, "id", idNumber))
|
||||
{
|
||||
log("No ID key found in Emote value");
|
||||
return;
|
||||
}
|
||||
|
||||
QString _code;
|
||||
if (!rj::getSafe(emoteJSON, "code", _code)) {
|
||||
if (!rj::getSafe(emoteJSON, "code", _code))
|
||||
{
|
||||
log("No code key found in Emote value");
|
||||
return;
|
||||
}
|
||||
@@ -478,13 +505,15 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
|
||||
|
||||
void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
|
||||
{
|
||||
if (!emoteSet) {
|
||||
if (!emoteSet)
|
||||
{
|
||||
log("null emote set sent");
|
||||
return;
|
||||
}
|
||||
|
||||
auto staticSetIt = this->staticEmoteSets.find(emoteSet->key);
|
||||
if (staticSetIt != this->staticEmoteSets.end()) {
|
||||
if (staticSetIt != this->staticEmoteSets.end())
|
||||
{
|
||||
const auto &staticSet = staticSetIt->second;
|
||||
emoteSet->channelName = staticSet.channelName;
|
||||
emoteSet->text = staticSet.text;
|
||||
@@ -503,18 +532,21 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
|
||||
|
||||
req.onSuccess([emoteSet](auto result) -> Outcome {
|
||||
auto root = result.parseRapidJson();
|
||||
if (!root.IsObject()) {
|
||||
if (!root.IsObject())
|
||||
{
|
||||
return Failure;
|
||||
}
|
||||
|
||||
std::string emoteSetID;
|
||||
QString channelName;
|
||||
QString type;
|
||||
if (!rj::getSafe(root, "channel_name", channelName)) {
|
||||
if (!rj::getSafe(root, "channel_name", channelName))
|
||||
{
|
||||
return Failure;
|
||||
}
|
||||
|
||||
if (!rj::getSafe(root, "type", type)) {
|
||||
if (!rj::getSafe(root, "type", type))
|
||||
{
|
||||
return Failure;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ TwitchAccountManager::TwitchAccountManager()
|
||||
|
||||
std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent()
|
||||
{
|
||||
if (!this->currentUser_) {
|
||||
if (!this->currentUser_)
|
||||
{
|
||||
return this->anonymousUser_;
|
||||
}
|
||||
|
||||
@@ -34,7 +35,8 @@ std::vector<QString> TwitchAccountManager::getUsernames() const
|
||||
|
||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||
|
||||
for (const auto &user : this->accounts.getVector()) {
|
||||
for (const auto &user : this->accounts.getVector())
|
||||
{
|
||||
userNames.push_back(user->getUserName());
|
||||
}
|
||||
|
||||
@@ -46,8 +48,10 @@ std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||
|
||||
for (const auto &user : this->accounts.getVector()) {
|
||||
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0) {
|
||||
for (const auto &user : this->accounts.getVector())
|
||||
{
|
||||
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -68,8 +72,10 @@ void TwitchAccountManager::reloadUsers()
|
||||
|
||||
bool listUpdated = false;
|
||||
|
||||
for (const auto &uid : keys) {
|
||||
if (uid == "current") {
|
||||
for (const auto &uid : keys)
|
||||
{
|
||||
if (uid == "current")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -83,7 +89,8 @@ void TwitchAccountManager::reloadUsers()
|
||||
"/accounts/" + uid + "/oauthToken");
|
||||
|
||||
if (username.empty() || userID.empty() || clientID.empty() ||
|
||||
oauthToken.empty()) {
|
||||
oauthToken.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -92,28 +99,37 @@ void TwitchAccountManager::reloadUsers()
|
||||
userData.clientID = qS(clientID).trimmed();
|
||||
userData.oauthToken = qS(oauthToken).trimmed();
|
||||
|
||||
switch (this->addUser(userData)) {
|
||||
case AddUserResponse::UserAlreadyExists: {
|
||||
switch (this->addUser(userData))
|
||||
{
|
||||
case AddUserResponse::UserAlreadyExists:
|
||||
{
|
||||
log("User {} already exists", userData.username);
|
||||
// Do nothing
|
||||
} break;
|
||||
case AddUserResponse::UserValuesUpdated: {
|
||||
}
|
||||
break;
|
||||
case AddUserResponse::UserValuesUpdated:
|
||||
{
|
||||
log("User {} already exists, and values updated!",
|
||||
userData.username);
|
||||
if (userData.username == this->getCurrent()->getUserName()) {
|
||||
if (userData.username == this->getCurrent()->getUserName())
|
||||
{
|
||||
log("It was the current user, so we need to reconnect "
|
||||
"stuff!");
|
||||
this->currentUserChanged.invoke();
|
||||
}
|
||||
} break;
|
||||
case AddUserResponse::UserAdded: {
|
||||
}
|
||||
break;
|
||||
case AddUserResponse::UserAdded:
|
||||
{
|
||||
log("Added user {}", userData.username);
|
||||
listUpdated = true;
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (listUpdated) {
|
||||
if (listUpdated)
|
||||
{
|
||||
this->userListUpdated.invoke();
|
||||
}
|
||||
}
|
||||
@@ -125,12 +141,15 @@ void TwitchAccountManager::load()
|
||||
this->currentUsername.connect([this](const auto &newValue, auto) {
|
||||
QString newUsername(QString::fromStdString(newValue));
|
||||
auto user = this->findUserByUsername(newUsername);
|
||||
if (user) {
|
||||
if (user)
|
||||
{
|
||||
log("[AccountManager:currentUsernameChanged] User successfully "
|
||||
"updated to {}",
|
||||
newUsername);
|
||||
this->currentUser_ = user;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
log("[AccountManager:currentUsernameChanged] User successfully "
|
||||
"updated to anonymous");
|
||||
this->currentUser_ = this->anonymousUser_;
|
||||
@@ -142,7 +161,8 @@ void TwitchAccountManager::load()
|
||||
|
||||
bool TwitchAccountManager::isLoggedIn() const
|
||||
{
|
||||
if (!this->currentUser_) {
|
||||
if (!this->currentUser_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -156,12 +176,14 @@ bool TwitchAccountManager::removeUser(TwitchAccount *account)
|
||||
const auto &accs = this->accounts.getVector();
|
||||
|
||||
std::string userID(account->getUserId().toStdString());
|
||||
if (!userID.empty()) {
|
||||
if (!userID.empty())
|
||||
{
|
||||
pajlada::Settings::SettingManager::removeSetting("/accounts/uid" +
|
||||
userID);
|
||||
}
|
||||
|
||||
if (account->getUserName() == qS(this->currentUsername.getValue())) {
|
||||
if (account->getUserName() == qS(this->currentUsername.getValue()))
|
||||
{
|
||||
// The user that was removed is the current user, log into the anonymous
|
||||
// user
|
||||
this->currentUsername = "";
|
||||
@@ -176,20 +198,26 @@ TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
|
||||
const TwitchAccountManager::UserData &userData)
|
||||
{
|
||||
auto previousUser = this->findUserByUsername(userData.username);
|
||||
if (previousUser) {
|
||||
if (previousUser)
|
||||
{
|
||||
bool userUpdated = false;
|
||||
|
||||
if (previousUser->setOAuthClient(userData.clientID)) {
|
||||
if (previousUser->setOAuthClient(userData.clientID))
|
||||
{
|
||||
userUpdated = true;
|
||||
}
|
||||
|
||||
if (previousUser->setOAuthToken(userData.oauthToken)) {
|
||||
if (previousUser->setOAuthToken(userData.oauthToken))
|
||||
{
|
||||
userUpdated = true;
|
||||
}
|
||||
|
||||
if (userUpdated) {
|
||||
if (userUpdated)
|
||||
{
|
||||
return AddUserResponse::UserValuesUpdated;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return AddUserResponse::UserAlreadyExists;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,25 +21,29 @@ void TwitchApi::findUserId(const QString user,
|
||||
request.setTimeout(30000);
|
||||
request.onSuccess([successCallback](auto result) mutable -> Outcome {
|
||||
auto root = result.parseJson();
|
||||
if (!root.value("users").isArray()) {
|
||||
if (!root.value("users").isArray())
|
||||
{
|
||||
log("API Error while getting user id, users is not an array");
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
auto users = root.value("users").toArray();
|
||||
if (users.size() != 1) {
|
||||
if (users.size() != 1)
|
||||
{
|
||||
log("API Error while getting user id, users array size is not 1");
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
if (!users[0].isObject()) {
|
||||
if (!users[0].isObject())
|
||||
{
|
||||
log("API Error while getting user id, first user is not an object");
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
auto firstUser = users[0].toObject();
|
||||
auto id = firstUser.value("_id");
|
||||
if (!id.isString()) {
|
||||
if (!id.isString())
|
||||
{
|
||||
log("API Error: while getting user id, first user object `_id` key "
|
||||
"is not a "
|
||||
"string");
|
||||
|
||||
@@ -24,11 +24,13 @@ void TwitchBadges::loadTwitchBadges()
|
||||
auto badgeSets = this->badgeSets_.access();
|
||||
|
||||
auto jsonSets = root.value("badge_sets").toObject();
|
||||
for (auto sIt = jsonSets.begin(); sIt != jsonSets.end(); ++sIt) {
|
||||
for (auto sIt = jsonSets.begin(); sIt != jsonSets.end(); ++sIt)
|
||||
{
|
||||
auto key = sIt.key();
|
||||
auto versions = sIt.value().toObject().value("versions").toObject();
|
||||
|
||||
for (auto vIt = versions.begin(); vIt != versions.end(); ++vIt) {
|
||||
for (auto vIt = versions.begin(); vIt != versions.end(); ++vIt)
|
||||
{
|
||||
auto versionObj = vIt.value().toObject();
|
||||
|
||||
auto emote = Emote{
|
||||
@@ -61,9 +63,11 @@ boost::optional<EmotePtr> TwitchBadges::badge(const QString &set,
|
||||
{
|
||||
auto badgeSets = this->badgeSets_.access();
|
||||
auto it = badgeSets->find(set);
|
||||
if (it != badgeSets->end()) {
|
||||
if (it != badgeSets->end())
|
||||
{
|
||||
auto it2 = it->second.find(version);
|
||||
if (it2 != it->second.end()) {
|
||||
if (it2 != it->second.end())
|
||||
{
|
||||
return it2->second;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,9 +34,11 @@ namespace {
|
||||
QJsonArray jsonMessages = jsonRoot.value("messages").toArray();
|
||||
std::vector<MessagePtr> messages;
|
||||
|
||||
if (jsonMessages.empty()) return messages;
|
||||
if (jsonMessages.empty())
|
||||
return messages;
|
||||
|
||||
for (const auto jsonMessage : jsonMessages) {
|
||||
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
|
||||
@@ -46,7 +48,8 @@ namespace {
|
||||
|
||||
MessageParseArgs args;
|
||||
TwitchMessageBuilder builder(channel.get(), privMsg, args);
|
||||
if (!builder.isIgnored()) {
|
||||
if (!builder.isIgnored())
|
||||
{
|
||||
messages.push_back(builder.build());
|
||||
}
|
||||
}
|
||||
@@ -63,8 +66,10 @@ namespace {
|
||||
// parse json
|
||||
QJsonObject jsonCategories = jsonRoot.value("chatters").toObject();
|
||||
|
||||
for (const auto &category : categories) {
|
||||
for (auto jsonCategory : jsonCategories.value(category).toArray()) {
|
||||
for (const auto &category : categories)
|
||||
{
|
||||
for (auto jsonCategory : jsonCategories.value(category).toArray())
|
||||
{
|
||||
usernames.insert(jsonCategory.toString());
|
||||
}
|
||||
}
|
||||
@@ -90,7 +95,8 @@ TwitchChannel::TwitchChannel(const QString &name,
|
||||
log("[TwitchChannel:{}] Opened", name);
|
||||
|
||||
this->liveStatusChanged.connect([this]() {
|
||||
if (this->isLive() == 1) {
|
||||
if (this->isLive() == 1)
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
@@ -168,7 +174,8 @@ void TwitchChannel::sendMessage(const QString &message)
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
if (!app->accounts->twitch.isLoggedIn()) {
|
||||
if (!app->accounts->twitch.isLoggedIn())
|
||||
{
|
||||
// XXX: It would be nice if we could add a link here somehow that opened
|
||||
// the "account manager" dialog
|
||||
this->addMessage(
|
||||
@@ -184,13 +191,17 @@ void TwitchChannel::sendMessage(const QString &message)
|
||||
|
||||
parsedMessage = parsedMessage.trimmed();
|
||||
|
||||
if (parsedMessage.isEmpty()) {
|
||||
if (parsedMessage.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->hasModRights()) {
|
||||
if (getSettings()->allowDuplicateMessages) {
|
||||
if (parsedMessage == this->lastSentMessage_) {
|
||||
if (!this->hasModRights())
|
||||
{
|
||||
if (getSettings()->allowDuplicateMessages)
|
||||
{
|
||||
if (parsedMessage == this->lastSentMessage_)
|
||||
{
|
||||
parsedMessage.append(this->messageSuffix_);
|
||||
}
|
||||
}
|
||||
@@ -199,7 +210,8 @@ void TwitchChannel::sendMessage(const QString &message)
|
||||
bool messageSent = false;
|
||||
this->sendMessageSignal.invoke(this->getName(), parsedMessage, messageSent);
|
||||
|
||||
if (messageSent) {
|
||||
if (messageSent)
|
||||
{
|
||||
qDebug() << "sent";
|
||||
this->lastSentMessage_ = parsedMessage;
|
||||
}
|
||||
@@ -212,7 +224,8 @@ bool TwitchChannel::isMod() const
|
||||
|
||||
void TwitchChannel::setMod(bool value)
|
||||
{
|
||||
if (this->mod_ != value) {
|
||||
if (this->mod_ != value)
|
||||
{
|
||||
this->mod_ = value;
|
||||
|
||||
this->userStateChanged.invoke();
|
||||
@@ -235,14 +248,16 @@ void TwitchChannel::addJoinedUser(const QString &user)
|
||||
{
|
||||
auto app = getApp();
|
||||
if (user == app->accounts->twitch.getCurrent()->getUserName() ||
|
||||
!getSettings()->showJoins.getValue()) {
|
||||
!getSettings()->showJoins.getValue())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto joinedUsers = this->joinedUsers_.access();
|
||||
joinedUsers->append(user);
|
||||
|
||||
if (!this->joinedUsersMergeQueued_) {
|
||||
if (!this->joinedUsersMergeQueued_)
|
||||
{
|
||||
this->joinedUsersMergeQueued_ = true;
|
||||
|
||||
QTimer::singleShot(500, &this->lifetimeGuard_, [this] {
|
||||
@@ -263,14 +278,16 @@ void TwitchChannel::addPartedUser(const QString &user)
|
||||
auto app = getApp();
|
||||
|
||||
if (user == app->accounts->twitch.getCurrent()->getUserName() ||
|
||||
!getSettings()->showJoins.getValue()) {
|
||||
!getSettings()->showJoins.getValue())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto partedUsers = this->partedUsers_.access();
|
||||
partedUsers->append(user);
|
||||
|
||||
if (!this->partedUsersMergeQueued_) {
|
||||
if (!this->partedUsersMergeQueued_)
|
||||
{
|
||||
this->partedUsersMergeQueued_ = true;
|
||||
|
||||
QTimer::singleShot(500, &this->lifetimeGuard_, [this] {
|
||||
@@ -294,7 +311,8 @@ QString TwitchChannel::roomId() const
|
||||
|
||||
void TwitchChannel::setRoomId(const QString &id)
|
||||
{
|
||||
if (*this->roomID_.accessConst() != id) {
|
||||
if (*this->roomID_.accessConst() != id)
|
||||
{
|
||||
*this->roomID_.access() = id;
|
||||
this->roomIdChanged.invoke();
|
||||
this->loadRecentMessages();
|
||||
@@ -350,7 +368,8 @@ boost::optional<EmotePtr> TwitchChannel::bttvEmote(const EmoteName &name) const
|
||||
auto emotes = this->bttvEmotes_.get();
|
||||
auto it = emotes->find(name);
|
||||
|
||||
if (it == emotes->end()) return boost::none;
|
||||
if (it == emotes->end())
|
||||
return boost::none;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
@@ -359,7 +378,8 @@ boost::optional<EmotePtr> TwitchChannel::ffzEmote(const EmoteName &name) const
|
||||
auto emotes = this->ffzEmotes_.get();
|
||||
auto it = emotes->find(name);
|
||||
|
||||
if (it == emotes->end()) return boost::none;
|
||||
if (it == emotes->end())
|
||||
return boost::none;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
@@ -393,25 +413,33 @@ void TwitchChannel::setLive(bool newLiveStatus)
|
||||
bool gotNewLiveStatus = false;
|
||||
{
|
||||
auto guard = this->streamStatus_.access();
|
||||
if (guard->live != newLiveStatus) {
|
||||
if (guard->live != newLiveStatus)
|
||||
{
|
||||
gotNewLiveStatus = true;
|
||||
if (newLiveStatus) {
|
||||
if (newLiveStatus)
|
||||
{
|
||||
if (getApp()->notifications->isChannelNotified(
|
||||
this->getName(), Platform::Twitch)) {
|
||||
if (Toasts::isEnabled()) {
|
||||
this->getName(), Platform::Twitch))
|
||||
{
|
||||
if (Toasts::isEnabled())
|
||||
{
|
||||
getApp()->toasts->sendChannelNotification(
|
||||
this->getName(), Platform::Twitch);
|
||||
}
|
||||
if (getSettings()->notificationPlaySound) {
|
||||
if (getSettings()->notificationPlaySound)
|
||||
{
|
||||
getApp()->notifications->playSound();
|
||||
}
|
||||
if (getSettings()->notificationFlashTaskbar) {
|
||||
if (getSettings()->notificationFlashTaskbar)
|
||||
{
|
||||
getApp()->windows->sendAlert();
|
||||
}
|
||||
}
|
||||
auto live = makeSystemMessage(this->getName() + " is live");
|
||||
this->addMessage(live);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
auto offline =
|
||||
makeSystemMessage(this->getName() + " is offline");
|
||||
this->addMessage(offline);
|
||||
@@ -420,7 +448,8 @@ void TwitchChannel::setLive(bool newLiveStatus)
|
||||
}
|
||||
}
|
||||
|
||||
if (gotNewLiveStatus) {
|
||||
if (gotNewLiveStatus)
|
||||
{
|
||||
this->liveStatusChanged.invoke();
|
||||
}
|
||||
}
|
||||
@@ -429,7 +458,8 @@ void TwitchChannel::refreshLiveStatus()
|
||||
{
|
||||
auto roomID = this->roomId();
|
||||
|
||||
if (roomID.isEmpty()) {
|
||||
if (roomID.isEmpty())
|
||||
{
|
||||
log("[TwitchChannel:{}] Refreshing live status (Missing ID)",
|
||||
this->getName());
|
||||
this->setLive(false);
|
||||
@@ -450,7 +480,8 @@ void TwitchChannel::refreshLiveStatus()
|
||||
request.onSuccess(
|
||||
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||
ChannelPtr shared = weak.lock();
|
||||
if (!shared) return Failure;
|
||||
if (!shared)
|
||||
return Failure;
|
||||
|
||||
return this->parseLiveStatus(result.parseRapidJson());
|
||||
});
|
||||
@@ -460,26 +491,30 @@ void TwitchChannel::refreshLiveStatus()
|
||||
|
||||
Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
||||
{
|
||||
if (!document.IsObject()) {
|
||||
if (!document.IsObject())
|
||||
{
|
||||
log("[TwitchChannel:refreshLiveStatus] root is not an object");
|
||||
return Failure;
|
||||
}
|
||||
|
||||
if (!document.HasMember("stream")) {
|
||||
if (!document.HasMember("stream"))
|
||||
{
|
||||
log("[TwitchChannel:refreshLiveStatus] Missing stream in root");
|
||||
return Failure;
|
||||
}
|
||||
|
||||
const auto &stream = document["stream"];
|
||||
|
||||
if (!stream.IsObject()) {
|
||||
if (!stream.IsObject())
|
||||
{
|
||||
// Stream is offline (stream is most likely null)
|
||||
this->setLive(false);
|
||||
return Failure;
|
||||
}
|
||||
|
||||
if (!stream.HasMember("viewers") || !stream.HasMember("game") ||
|
||||
!stream.HasMember("channel") || !stream.HasMember("created_at")) {
|
||||
!stream.HasMember("channel") || !stream.HasMember("created_at"))
|
||||
{
|
||||
log("[TwitchChannel:refreshLiveStatus] Missing members in stream");
|
||||
this->setLive(false);
|
||||
return Failure;
|
||||
@@ -487,7 +522,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
||||
|
||||
const rapidjson::Value &streamChannel = stream["channel"];
|
||||
|
||||
if (!streamChannel.IsObject() || !streamChannel.HasMember("status")) {
|
||||
if (!streamChannel.IsObject() || !streamChannel.HasMember("status"))
|
||||
{
|
||||
log("[TwitchChannel:refreshLiveStatus] Missing member \"status\" in "
|
||||
"channel");
|
||||
return Failure;
|
||||
@@ -507,19 +543,25 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
||||
QString::number(diff % 3600 / 60) + "m";
|
||||
|
||||
status->rerun = false;
|
||||
if (stream.HasMember("stream_type")) {
|
||||
if (stream.HasMember("stream_type"))
|
||||
{
|
||||
status->streamType = stream["stream_type"].GetString();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
status->streamType = QString();
|
||||
}
|
||||
|
||||
if (stream.HasMember("broadcast_platform")) {
|
||||
if (stream.HasMember("broadcast_platform"))
|
||||
{
|
||||
const auto &broadcastPlatformValue = stream["broadcast_platform"];
|
||||
|
||||
if (broadcastPlatformValue.IsString()) {
|
||||
if (broadcastPlatformValue.IsString())
|
||||
{
|
||||
const char *broadcastPlatform =
|
||||
stream["broadcast_platform"].GetString();
|
||||
if (strcmp(broadcastPlatform, "rerun") == 0) {
|
||||
if (strcmp(broadcastPlatform, "rerun") == 0)
|
||||
{
|
||||
status->rerun = true;
|
||||
}
|
||||
}
|
||||
@@ -546,7 +588,8 @@ void TwitchChannel::loadRecentMessages()
|
||||
|
||||
request.onSuccess([weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||
auto shared = weak.lock();
|
||||
if (!shared) return Failure;
|
||||
if (!shared)
|
||||
return Failure;
|
||||
|
||||
auto messages = parseRecentMessages(result.parseJson(), shared);
|
||||
|
||||
@@ -561,9 +604,11 @@ void TwitchChannel::loadRecentMessages()
|
||||
void TwitchChannel::refreshPubsub()
|
||||
{
|
||||
// listen to moderation actions
|
||||
if (!this->hasModRights()) return;
|
||||
if (!this->hasModRights())
|
||||
return;
|
||||
auto roomId = this->roomId();
|
||||
if (roomId.isEmpty()) return;
|
||||
if (roomId.isEmpty())
|
||||
return;
|
||||
|
||||
auto account = getApp()->accounts->twitch.getCurrent();
|
||||
getApp()->twitch2->pubsub->listenToChannelModerationActions(roomId,
|
||||
@@ -575,9 +620,11 @@ void TwitchChannel::refreshChatters()
|
||||
// setting?
|
||||
const auto streamStatus = this->accessStreamStatus();
|
||||
|
||||
if (getSettings()->onlyFetchChattersForSmallerStreamers) {
|
||||
if (getSettings()->onlyFetchChattersForSmallerStreamers)
|
||||
{
|
||||
if (streamStatus->live &&
|
||||
streamStatus->viewerCount > getSettings()->smallStreamerLimit) {
|
||||
streamStatus->viewerCount > getSettings()->smallStreamerLimit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -591,10 +638,12 @@ void TwitchChannel::refreshChatters()
|
||||
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||
// channel still exists?
|
||||
auto shared = weak.lock();
|
||||
if (!shared) return Failure;
|
||||
if (!shared)
|
||||
return Failure;
|
||||
|
||||
auto pair = parseChatters(result.parseJson());
|
||||
if (pair.first) {
|
||||
if (pair.first)
|
||||
{
|
||||
*this->chatters_.access() = std::move(pair.second);
|
||||
}
|
||||
|
||||
@@ -613,7 +662,8 @@ void TwitchChannel::refreshBadges()
|
||||
|
||||
req.onSuccess([this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||
auto shared = weak.lock();
|
||||
if (!shared) return Failure;
|
||||
if (!shared)
|
||||
return Failure;
|
||||
|
||||
auto badgeSets = this->badgeSets_.access();
|
||||
|
||||
@@ -621,12 +671,14 @@ void TwitchChannel::refreshBadges()
|
||||
|
||||
auto _ = jsonRoot["badge_sets"].toObject();
|
||||
for (auto jsonBadgeSet = _.begin(); jsonBadgeSet != _.end();
|
||||
jsonBadgeSet++) {
|
||||
jsonBadgeSet++)
|
||||
{
|
||||
auto &versions = (*badgeSets)[jsonBadgeSet.key()];
|
||||
|
||||
auto _ = jsonBadgeSet->toObject()["versions"].toObject();
|
||||
for (auto jsonVersion_ = _.begin(); jsonVersion_ != _.end();
|
||||
jsonVersion_++) {
|
||||
jsonVersion_++)
|
||||
{
|
||||
auto jsonVersion = jsonVersion_->toObject();
|
||||
auto emote = std::make_shared<Emote>(Emote{
|
||||
EmoteName{},
|
||||
@@ -719,9 +771,11 @@ boost::optional<EmotePtr> TwitchChannel::twitchBadge(
|
||||
{
|
||||
auto badgeSets = this->badgeSets_.access();
|
||||
auto it = badgeSets->find(set);
|
||||
if (it != badgeSets->end()) {
|
||||
if (it != badgeSets->end())
|
||||
{
|
||||
auto it2 = it->second.find(version);
|
||||
if (it2 != it->second.end()) {
|
||||
if (it2 != it->second.end())
|
||||
{
|
||||
return it2->second;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ EmotePtr TwitchEmotes::getOrCreateEmote(const EmoteId &id,
|
||||
|
||||
// replace regexes
|
||||
auto it = replacements.find(name);
|
||||
if (it != replacements.end()) {
|
||||
if (it != replacements.end())
|
||||
{
|
||||
name = it.value();
|
||||
}
|
||||
|
||||
@@ -45,7 +46,8 @@ EmotePtr TwitchEmotes::getOrCreateEmote(const EmoteId &id,
|
||||
auto cache = this->twitchEmotesCache_.access();
|
||||
auto shared = (*cache)[id].lock();
|
||||
|
||||
if (!shared) {
|
||||
if (!shared)
|
||||
{
|
||||
(*cache)[id] = shared = std::make_shared<Emote>(
|
||||
Emote{EmoteName{name},
|
||||
ImageSet{
|
||||
|
||||
@@ -5,7 +5,8 @@ namespace chatterino {
|
||||
|
||||
bool trimChannelName(const QString &channelName, QString &outChannelName)
|
||||
{
|
||||
if (channelName.length() < 2) {
|
||||
if (channelName.length() < 2)
|
||||
{
|
||||
log("channel name length below 2");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -59,8 +59,10 @@ bool TwitchMessageBuilder::isIgnored() const
|
||||
auto app = getApp();
|
||||
|
||||
// TODO(pajlada): Do we need to check if the phrase is valid first?
|
||||
for (const auto &phrase : app->ignores->phrases.getVector()) {
|
||||
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_)) {
|
||||
for (const auto &phrase : app->ignores->phrases.getVector())
|
||||
{
|
||||
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_))
|
||||
{
|
||||
log("Blocking message because it contains ignored phrase {}",
|
||||
phrase.getPattern());
|
||||
return true;
|
||||
@@ -68,12 +70,15 @@ bool TwitchMessageBuilder::isIgnored() const
|
||||
}
|
||||
|
||||
if (getSettings()->enableTwitchIgnoredUsers &&
|
||||
this->tags.contains("user-id")) {
|
||||
this->tags.contains("user-id"))
|
||||
{
|
||||
auto sourceUserID = this->tags.value("user-id").toString();
|
||||
|
||||
for (const auto &user :
|
||||
app->accounts->twitch.getCurrent()->getIgnores()) {
|
||||
if (sourceUserID == user.id) {
|
||||
app->accounts->twitch.getCurrent()->getIgnores())
|
||||
{
|
||||
if (sourceUserID == user.id)
|
||||
{
|
||||
log("Blocking message because it's from blocked user {}",
|
||||
user.name);
|
||||
return true;
|
||||
@@ -89,7 +94,8 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
// PARSING
|
||||
this->parseUsername();
|
||||
|
||||
if (this->userName == this->channel->getName()) {
|
||||
if (this->userName == this->channel->getName())
|
||||
{
|
||||
this->senderIsBroadcaster = true;
|
||||
}
|
||||
|
||||
@@ -104,32 +110,42 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
|
||||
// timestamp
|
||||
bool isPastMsg = this->tags.contains("historical");
|
||||
if (isPastMsg) {
|
||||
if (isPastMsg)
|
||||
{
|
||||
// This may be architecture dependent(datatype)
|
||||
qint64 ts = this->tags.value("tmi-sent-ts").toLongLong();
|
||||
QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(ts);
|
||||
this->emplace<TimestampElement>(dateTime.time());
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
this->emplace<TimestampElement>();
|
||||
}
|
||||
|
||||
bool addModerationElement = true;
|
||||
if (this->senderIsBroadcaster) {
|
||||
if (this->senderIsBroadcaster)
|
||||
{
|
||||
addModerationElement = false;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
bool hasUserType = this->tags.contains("user-type");
|
||||
if (hasUserType) {
|
||||
if (hasUserType)
|
||||
{
|
||||
QString userType = this->tags.value("user-type").toString();
|
||||
|
||||
if (userType == "mod") {
|
||||
if (!args.isStaffOrBroadcaster) {
|
||||
if (userType == "mod")
|
||||
{
|
||||
if (!args.isStaffOrBroadcaster)
|
||||
{
|
||||
addModerationElement = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (addModerationElement) {
|
||||
if (addModerationElement)
|
||||
{
|
||||
this->emplace<TwitchModerationElement>();
|
||||
}
|
||||
|
||||
@@ -144,7 +160,8 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
|
||||
// QString bits;
|
||||
auto iterator = this->tags.find("bits");
|
||||
if (iterator != this->tags.end()) {
|
||||
if (iterator != this->tags.end())
|
||||
{
|
||||
this->hasBits_ = true;
|
||||
// bits = iterator.value().toString();
|
||||
}
|
||||
@@ -153,10 +170,12 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
std::vector<std::tuple<int, EmotePtr, EmoteName>> twitchEmotes;
|
||||
|
||||
iterator = this->tags.find("emotes");
|
||||
if (iterator != this->tags.end()) {
|
||||
if (iterator != this->tags.end())
|
||||
{
|
||||
QStringList emoteString = iterator.value().toString().split('/');
|
||||
|
||||
for (QString emote : emoteString) {
|
||||
for (QString emote : emoteString)
|
||||
{
|
||||
this->appendTwitchEmote(emote, twitchEmotes);
|
||||
}
|
||||
}
|
||||
@@ -172,8 +191,10 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
return !((std::get<0>(item) >= pos) &&
|
||||
std::get<0>(item) < (pos + len));
|
||||
});
|
||||
for (auto copy = it; copy != twitchEmotes.end(); ++copy) {
|
||||
if (std::get<1>(*copy) == nullptr) {
|
||||
for (auto copy = it; copy != twitchEmotes.end(); ++copy)
|
||||
{
|
||||
if (std::get<1>(*copy) == nullptr)
|
||||
{
|
||||
log("remem nullptr {}", std::get<2>(*copy).string);
|
||||
}
|
||||
}
|
||||
@@ -184,9 +205,11 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
};
|
||||
|
||||
auto shiftIndicesAfter = [&twitchEmotes](int pos, int by) mutable {
|
||||
for (auto &item : twitchEmotes) {
|
||||
for (auto &item : twitchEmotes)
|
||||
{
|
||||
auto &index = std::get<0>(item);
|
||||
if (index >= pos) {
|
||||
if (index >= pos)
|
||||
{
|
||||
index += by;
|
||||
}
|
||||
}
|
||||
@@ -195,16 +218,21 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
auto addReplEmotes = [&twitchEmotes](const IgnorePhrase &phrase,
|
||||
const QStringRef &midrepl,
|
||||
int startIndex) mutable {
|
||||
if (!phrase.containsEmote()) {
|
||||
if (!phrase.containsEmote())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<QStringRef> words = midrepl.split(' ');
|
||||
int pos = 0;
|
||||
for (const auto &word : words) {
|
||||
for (const auto &emote : phrase.getEmotes()) {
|
||||
if (word == emote.first.string) {
|
||||
if (emote.second == nullptr) {
|
||||
for (const auto &word : words)
|
||||
{
|
||||
for (const auto &emote : phrase.getEmotes())
|
||||
{
|
||||
if (word == emote.first.string)
|
||||
{
|
||||
if (emote.second == nullptr)
|
||||
{
|
||||
log("emote null {}", emote.first.string);
|
||||
}
|
||||
twitchEmotes.push_back(std::tuple<int, EmotePtr, EmoteName>{
|
||||
@@ -215,19 +243,24 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto &phrase : phrases) {
|
||||
if (phrase.isBlock()) {
|
||||
for (const auto &phrase : phrases)
|
||||
{
|
||||
if (phrase.isBlock())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (phrase.isRegex()) {
|
||||
if (phrase.isRegex())
|
||||
{
|
||||
const auto ®ex = phrase.getRegex();
|
||||
if (!regex.isValid()) {
|
||||
if (!regex.isValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
QRegularExpressionMatch match;
|
||||
int from = 0;
|
||||
while ((from = this->originalMessage_.indexOf(regex, from,
|
||||
&match)) != -1) {
|
||||
&match)) != -1)
|
||||
{
|
||||
int len = match.capturedLength();
|
||||
auto vret = removeEmotesInRange(from, len, twitchEmotes);
|
||||
auto mid = this->originalMessage_.mid(from, len);
|
||||
@@ -236,15 +269,19 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
int midsize = mid.size();
|
||||
this->originalMessage_.replace(from, len, mid);
|
||||
int pos1 = from;
|
||||
while (pos1 > 0) {
|
||||
if (this->originalMessage_[pos1 - 1] == ' ') {
|
||||
while (pos1 > 0)
|
||||
{
|
||||
if (this->originalMessage_[pos1 - 1] == ' ')
|
||||
{
|
||||
break;
|
||||
}
|
||||
--pos1;
|
||||
}
|
||||
int pos2 = from + midsize;
|
||||
while (pos2 < this->originalMessage_.length()) {
|
||||
if (this->originalMessage_[pos2] == ' ') {
|
||||
while (pos2 < this->originalMessage_.length())
|
||||
{
|
||||
if (this->originalMessage_[pos2] == ' ')
|
||||
{
|
||||
break;
|
||||
}
|
||||
++pos2;
|
||||
@@ -255,8 +292,10 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
auto midExtendedRef =
|
||||
this->originalMessage_.midRef(pos1, pos2 - pos1);
|
||||
|
||||
for (auto &tup : vret) {
|
||||
if (std::get<1>(tup) == nullptr) {
|
||||
for (auto &tup : vret)
|
||||
{
|
||||
if (std::get<1>(tup) == nullptr)
|
||||
{
|
||||
log("v nullptr {}", std::get<2>(tup).string);
|
||||
continue;
|
||||
}
|
||||
@@ -264,9 +303,11 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
"\\b" + std::get<2>(tup).string + "\\b",
|
||||
QRegularExpression::UseUnicodePropertiesOption);
|
||||
auto match = emoteregex.match(midExtendedRef);
|
||||
if (match.hasMatch()) {
|
||||
if (match.hasMatch())
|
||||
{
|
||||
int last = match.lastCapturedIndex();
|
||||
for (int i = 0; i <= last; ++i) {
|
||||
for (int i = 0; i <= last; ++i)
|
||||
{
|
||||
std::get<0>(tup) = from + match.capturedStart();
|
||||
twitchEmotes.push_back(std::move(tup));
|
||||
}
|
||||
@@ -277,14 +318,18 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
|
||||
from += midsize;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto &pattern = phrase.getPattern();
|
||||
if (pattern.isEmpty()) {
|
||||
if (pattern.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int from = 0;
|
||||
while ((from = this->originalMessage_.indexOf(
|
||||
pattern, from, phrase.caseSensitivity())) != -1) {
|
||||
pattern, from, phrase.caseSensitivity())) != -1)
|
||||
{
|
||||
int len = pattern.size();
|
||||
auto vret = removeEmotesInRange(from, len, twitchEmotes);
|
||||
auto replace = phrase.getReplace();
|
||||
@@ -293,15 +338,19 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
this->originalMessage_.replace(from, len, replace);
|
||||
|
||||
int pos1 = from;
|
||||
while (pos1 > 0) {
|
||||
if (this->originalMessage_[pos1 - 1] == ' ') {
|
||||
while (pos1 > 0)
|
||||
{
|
||||
if (this->originalMessage_[pos1 - 1] == ' ')
|
||||
{
|
||||
break;
|
||||
}
|
||||
--pos1;
|
||||
}
|
||||
int pos2 = from + replacesize;
|
||||
while (pos2 < this->originalMessage_.length()) {
|
||||
if (this->originalMessage_[pos2] == ' ') {
|
||||
while (pos2 < this->originalMessage_.length())
|
||||
{
|
||||
if (this->originalMessage_[pos2] == ' ')
|
||||
{
|
||||
break;
|
||||
}
|
||||
++pos2;
|
||||
@@ -312,8 +361,10 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
auto midExtendedRef =
|
||||
this->originalMessage_.midRef(pos1, pos2 - pos1);
|
||||
|
||||
for (auto &tup : vret) {
|
||||
if (std::get<1>(tup) == nullptr) {
|
||||
for (auto &tup : vret)
|
||||
{
|
||||
if (std::get<1>(tup) == nullptr)
|
||||
{
|
||||
log("v nullptr {}", std::get<2>(tup).string);
|
||||
continue;
|
||||
}
|
||||
@@ -321,9 +372,11 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
"\\b" + std::get<2>(tup).string + "\\b",
|
||||
QRegularExpression::UseUnicodePropertiesOption);
|
||||
auto match = emoteregex.match(midExtendedRef);
|
||||
if (match.hasMatch()) {
|
||||
if (match.hasMatch())
|
||||
{
|
||||
int last = match.lastCapturedIndex();
|
||||
for (int i = 0; i <= last; ++i) {
|
||||
for (int i = 0; i <= last; ++i)
|
||||
{
|
||||
std::get<0>(tup) = from + match.capturedStart();
|
||||
twitchEmotes.push_back(std::move(tup));
|
||||
}
|
||||
@@ -365,16 +418,20 @@ void TwitchMessageBuilder::addWords(
|
||||
auto i = int();
|
||||
auto currentTwitchEmote = twitchEmotes.begin();
|
||||
|
||||
for (const auto &word : words) {
|
||||
for (const auto &word : words)
|
||||
{
|
||||
// check if it's a twitch emote twitch emote
|
||||
while (currentTwitchEmote != twitchEmotes.end() &&
|
||||
std::get<0>(*currentTwitchEmote) < i) {
|
||||
std::get<0>(*currentTwitchEmote) < i)
|
||||
{
|
||||
++currentTwitchEmote;
|
||||
}
|
||||
if (currentTwitchEmote != twitchEmotes.end() &&
|
||||
std::get<0>(*currentTwitchEmote) == i) {
|
||||
std::get<0>(*currentTwitchEmote) == i)
|
||||
{
|
||||
auto emoteImage = std::get<1>(*currentTwitchEmote);
|
||||
if (emoteImage == nullptr) {
|
||||
if (emoteImage == nullptr)
|
||||
{
|
||||
log("emoteImage nullptr {}",
|
||||
std::get<2>(*currentTwitchEmote).string);
|
||||
}
|
||||
@@ -388,15 +445,18 @@ void TwitchMessageBuilder::addWords(
|
||||
}
|
||||
|
||||
// split words
|
||||
for (auto &variant : getApp()->emotes->emojis.parse(word)) {
|
||||
for (auto &variant : getApp()->emotes->emojis.parse(word))
|
||||
{
|
||||
boost::apply_visitor([&](auto &&arg) { this->addTextOrEmoji(arg); },
|
||||
variant);
|
||||
}
|
||||
|
||||
for (int j = 0; j < word.size(); j++) {
|
||||
for (int j = 0; j < word.size(); j++)
|
||||
{
|
||||
i++;
|
||||
|
||||
if (word.at(j).isHighSurrogate()) {
|
||||
if (word.at(j).isHighSurrogate())
|
||||
{
|
||||
j++;
|
||||
}
|
||||
}
|
||||
@@ -414,7 +474,8 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
|
||||
{
|
||||
auto string = QString(string_);
|
||||
|
||||
if (this->hasBits_ && this->tryParseCheermote(string)) {
|
||||
if (this->hasBits_ && this->tryParseCheermote(string))
|
||||
{
|
||||
// This string was parsed as a cheermote
|
||||
return;
|
||||
}
|
||||
@@ -424,7 +485,8 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
|
||||
// Emote name: "forsenPuke" - if string in ignoredEmotes
|
||||
// Will match emote regardless of source (i.e. bttv, ffz)
|
||||
// Emote source + name: "bttv:nyanPls"
|
||||
if (this->tryAppendEmote({string})) {
|
||||
if (this->tryAppendEmote({string}))
|
||||
{
|
||||
// Successfully appended an emote
|
||||
return;
|
||||
}
|
||||
@@ -435,28 +497,37 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
|
||||
auto textColor = this->action_ ? MessageColor(this->usernameColor_)
|
||||
: MessageColor(MessageColor::Text);
|
||||
|
||||
if (linkString.isEmpty()) {
|
||||
if (string.startsWith('@')) {
|
||||
if (linkString.isEmpty())
|
||||
{
|
||||
if (string.startsWith('@'))
|
||||
{
|
||||
this->emplace<TextElement>(string, MessageElementFlag::BoldUsername,
|
||||
textColor, FontStyle::ChatMediumBold);
|
||||
this->emplace<TextElement>(
|
||||
string, MessageElementFlag::NonBoldUsername, textColor);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
this->emplace<TextElement>(string, MessageElementFlag::Text,
|
||||
textColor);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
static QRegularExpression domainRegex(
|
||||
R"(^(?:(?:ftp|http)s?:\/\/)?([^\/]+)(?:\/.*)?$)",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
|
||||
QString lowercaseLinkString;
|
||||
auto match = domainRegex.match(string);
|
||||
if (match.isValid()) {
|
||||
if (match.isValid())
|
||||
{
|
||||
lowercaseLinkString = string.mid(0, match.capturedStart(1)) +
|
||||
match.captured(1).toLower() +
|
||||
string.mid(match.capturedEnd(1));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
lowercaseLinkString = string;
|
||||
}
|
||||
link = Link(Link::Url, linkString);
|
||||
@@ -475,12 +546,14 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
|
||||
LinkResolver::getLinkInfo(
|
||||
linkString, [linkMELowercase, linkMEOriginal, linkString](
|
||||
QString tooltipText, Link originalLink) {
|
||||
if (!tooltipText.isEmpty()) {
|
||||
if (!tooltipText.isEmpty())
|
||||
{
|
||||
linkMELowercase->setTooltip(tooltipText);
|
||||
linkMEOriginal->setTooltip(tooltipText);
|
||||
}
|
||||
if (originalLink.value != linkString &&
|
||||
!originalLink.value.isEmpty()) {
|
||||
!originalLink.value.isEmpty())
|
||||
{
|
||||
linkMELowercase->setLink(originalLink)->updateLink();
|
||||
linkMEOriginal->setLink(originalLink)->updateLink();
|
||||
}
|
||||
@@ -526,23 +599,27 @@ void TwitchMessageBuilder::parseMessageID()
|
||||
{
|
||||
auto iterator = this->tags.find("id");
|
||||
|
||||
if (iterator != this->tags.end()) {
|
||||
if (iterator != this->tags.end())
|
||||
{
|
||||
this->messageID = iterator.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::parseRoomID()
|
||||
{
|
||||
if (this->twitchChannel == nullptr) {
|
||||
if (this->twitchChannel == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto iterator = this->tags.find("room-id");
|
||||
|
||||
if (iterator != std::end(this->tags)) {
|
||||
if (iterator != std::end(this->tags))
|
||||
{
|
||||
this->roomID_ = iterator.value().toString();
|
||||
|
||||
if (this->twitchChannel->roomId().isEmpty()) {
|
||||
if (this->twitchChannel->roomId().isEmpty())
|
||||
{
|
||||
this->twitchChannel->setRoomId(this->roomID_);
|
||||
}
|
||||
}
|
||||
@@ -561,14 +638,16 @@ void TwitchMessageBuilder::appendChannelName()
|
||||
void TwitchMessageBuilder::parseUsername()
|
||||
{
|
||||
auto iterator = this->tags.find("color");
|
||||
if (iterator != this->tags.end()) {
|
||||
if (iterator != this->tags.end())
|
||||
{
|
||||
this->usernameColor_ = QColor(iterator.value().toString());
|
||||
}
|
||||
|
||||
// username
|
||||
this->userName = this->ircMessage->nick();
|
||||
|
||||
if (this->userName.isEmpty() || this->args.trimSubscriberUsername) {
|
||||
if (this->userName.isEmpty() || this->args.trimSubscriberUsername)
|
||||
{
|
||||
this->userName = this->tags.value(QLatin1String("login")).toString();
|
||||
}
|
||||
|
||||
@@ -591,16 +670,20 @@ void TwitchMessageBuilder::appendUsername()
|
||||
QString localizedName;
|
||||
|
||||
auto iterator = this->tags.find("display-name");
|
||||
if (iterator != this->tags.end()) {
|
||||
if (iterator != this->tags.end())
|
||||
{
|
||||
QString displayName =
|
||||
parseTagString(iterator.value().toString()).trimmed();
|
||||
|
||||
if (QString::compare(displayName, this->userName,
|
||||
Qt::CaseInsensitive) == 0) {
|
||||
Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
username = displayName;
|
||||
|
||||
this->message().displayName = displayName;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
localizedName = displayName;
|
||||
|
||||
this->message().displayName = username;
|
||||
@@ -617,34 +700,50 @@ void TwitchMessageBuilder::appendUsername()
|
||||
"/appearance/messages/usernameDisplayMode",
|
||||
UsernameDisplayMode::UsernameAndLocalizedName);
|
||||
|
||||
switch (usernameDisplayMode.getValue()) {
|
||||
case UsernameDisplayMode::Username: {
|
||||
switch (usernameDisplayMode.getValue())
|
||||
{
|
||||
case UsernameDisplayMode::Username:
|
||||
{
|
||||
usernameText = username;
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
case UsernameDisplayMode::LocalizedName: {
|
||||
if (hasLocalizedName) {
|
||||
case UsernameDisplayMode::LocalizedName:
|
||||
{
|
||||
if (hasLocalizedName)
|
||||
{
|
||||
usernameText = localizedName;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
usernameText = username;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
case UsernameDisplayMode::UsernameAndLocalizedName: {
|
||||
if (hasLocalizedName) {
|
||||
case UsernameDisplayMode::UsernameAndLocalizedName:
|
||||
{
|
||||
if (hasLocalizedName)
|
||||
{
|
||||
usernameText = username + "(" + localizedName + ")";
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
usernameText = username;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->args.isSentWhisper) {
|
||||
if (this->args.isSentWhisper)
|
||||
{
|
||||
// TODO(pajlada): Re-implement
|
||||
// userDisplayString +=
|
||||
// IrcManager::getInstance().getUser().getUserName();
|
||||
} else if (this->args.isReceivedWhisper) {
|
||||
}
|
||||
else if (this->args.isReceivedWhisper)
|
||||
{
|
||||
// Sender username
|
||||
this->emplace<TextElement>(usernameText, MessageElementFlag::Username,
|
||||
this->usernameColor_,
|
||||
@@ -659,7 +758,8 @@ void TwitchMessageBuilder::appendUsername()
|
||||
FontStyle::ChatMedium);
|
||||
|
||||
QColor selfColor = currentUser->color();
|
||||
if (!selfColor.isValid()) {
|
||||
if (!selfColor.isValid())
|
||||
{
|
||||
selfColor = app->themes->messages.textColors.system;
|
||||
}
|
||||
|
||||
@@ -667,8 +767,11 @@ void TwitchMessageBuilder::appendUsername()
|
||||
this->emplace<TextElement>(currentUser->getUserName() + ":",
|
||||
MessageElementFlag::Username, selfColor,
|
||||
FontStyle::ChatMediumBold);
|
||||
} else {
|
||||
if (!this->action_) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!this->action_)
|
||||
{
|
||||
usernameText += ":";
|
||||
}
|
||||
|
||||
@@ -690,7 +793,8 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
|
||||
|
||||
QString currentUsername = currentUser->getUserName();
|
||||
|
||||
if (this->ircMessage->nick() == currentUsername) {
|
||||
if (this->ircMessage->nick() == currentUsername)
|
||||
{
|
||||
currentUser->setColor(this->usernameColor_);
|
||||
// Do nothing. Highlights cannot be triggered by yourself
|
||||
return;
|
||||
@@ -698,14 +802,18 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
|
||||
|
||||
// update the media player url if necessary
|
||||
QUrl highlightSoundUrl;
|
||||
if (getSettings()->customHighlightSound) {
|
||||
if (getSettings()->customHighlightSound)
|
||||
{
|
||||
highlightSoundUrl =
|
||||
QUrl::fromLocalFile(getSettings()->pathHighlightSound.getValue());
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
highlightSoundUrl = QUrl("qrc:/sounds/ping2.wav");
|
||||
}
|
||||
|
||||
if (currentPlayerUrl != highlightSoundUrl) {
|
||||
if (currentPlayerUrl != highlightSoundUrl)
|
||||
{
|
||||
player->setMedia(highlightSoundUrl);
|
||||
|
||||
currentPlayerUrl = highlightSoundUrl;
|
||||
@@ -718,7 +826,8 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
|
||||
std::vector<HighlightPhrase> userHighlights =
|
||||
app->highlights->highlightedUsers.getVector();
|
||||
|
||||
if (getSettings()->enableSelfHighlight && currentUsername.size() > 0) {
|
||||
if (getSettings()->enableSelfHighlight && currentUsername.size() > 0)
|
||||
{
|
||||
HighlightPhrase selfHighlight(
|
||||
currentUsername, getSettings()->enableSelfHighlightTaskbar,
|
||||
getSettings()->enableSelfHighlightSound, false);
|
||||
@@ -731,22 +840,28 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
|
||||
|
||||
bool hasFocus = (QApplication::focusWidget() != nullptr);
|
||||
|
||||
if (!app->highlights->blacklistContains(this->ircMessage->nick())) {
|
||||
for (const HighlightPhrase &highlight : activeHighlights) {
|
||||
if (highlight.isMatch(this->originalMessage_)) {
|
||||
if (!app->highlights->blacklistContains(this->ircMessage->nick()))
|
||||
{
|
||||
for (const HighlightPhrase &highlight : activeHighlights)
|
||||
{
|
||||
if (highlight.isMatch(this->originalMessage_))
|
||||
{
|
||||
log("Highlight because {} matches {}", this->originalMessage_,
|
||||
highlight.getPattern());
|
||||
doHighlight = true;
|
||||
|
||||
if (highlight.getAlert()) {
|
||||
if (highlight.getAlert())
|
||||
{
|
||||
doAlert = true;
|
||||
}
|
||||
|
||||
if (highlight.getSound()) {
|
||||
if (highlight.getSound())
|
||||
{
|
||||
playSound = true;
|
||||
}
|
||||
|
||||
if (playSound && doAlert) {
|
||||
if (playSound && doAlert)
|
||||
{
|
||||
// Break if no further action can be taken from other
|
||||
// highlights This might change if highlights can have
|
||||
// custom colors/sounds/actions
|
||||
@@ -754,21 +869,26 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const HighlightPhrase &userHighlight : userHighlights) {
|
||||
if (userHighlight.isMatch(this->ircMessage->nick())) {
|
||||
for (const HighlightPhrase &userHighlight : userHighlights)
|
||||
{
|
||||
if (userHighlight.isMatch(this->ircMessage->nick()))
|
||||
{
|
||||
log("Highlight because user {} sent a message",
|
||||
this->ircMessage->nick());
|
||||
doHighlight = true;
|
||||
|
||||
if (userHighlight.getAlert()) {
|
||||
if (userHighlight.getAlert())
|
||||
{
|
||||
doAlert = true;
|
||||
}
|
||||
|
||||
if (userHighlight.getSound()) {
|
||||
if (userHighlight.getSound())
|
||||
{
|
||||
playSound = true;
|
||||
}
|
||||
|
||||
if (playSound && doAlert) {
|
||||
if (playSound && doAlert)
|
||||
{
|
||||
// Break if no further action can be taken from other
|
||||
// usernames Mostly used for regex stuff
|
||||
break;
|
||||
@@ -776,24 +896,30 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
|
||||
}
|
||||
}
|
||||
if (this->args.isReceivedWhisper &&
|
||||
getSettings()->enableWhisperHighlight) {
|
||||
if (getSettings()->enableWhisperHighlightTaskbar) {
|
||||
getSettings()->enableWhisperHighlight)
|
||||
{
|
||||
if (getSettings()->enableWhisperHighlightTaskbar)
|
||||
{
|
||||
doAlert = true;
|
||||
}
|
||||
if (getSettings()->enableWhisperHighlightSound) {
|
||||
if (getSettings()->enableWhisperHighlightSound)
|
||||
{
|
||||
playSound = true;
|
||||
}
|
||||
}
|
||||
|
||||
this->message().flags.set(MessageFlag::Highlighted, doHighlight);
|
||||
|
||||
if (!isPastMsg) {
|
||||
if (!isPastMsg)
|
||||
{
|
||||
if (playSound &&
|
||||
(!hasFocus || getSettings()->highlightAlwaysPlaySound)) {
|
||||
(!hasFocus || getSettings()->highlightAlwaysPlaySound))
|
||||
{
|
||||
player->play();
|
||||
}
|
||||
|
||||
if (doAlert) {
|
||||
if (doAlert)
|
||||
{
|
||||
getApp()->windows->sendAlert();
|
||||
}
|
||||
}
|
||||
@@ -805,13 +931,15 @@ void TwitchMessageBuilder::appendTwitchEmote(
|
||||
std::vector<std::tuple<int, EmotePtr, EmoteName>> &vec)
|
||||
{
|
||||
auto app = getApp();
|
||||
if (!emote.contains(':')) {
|
||||
if (!emote.contains(':'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto parameters = emote.split(':');
|
||||
|
||||
if (parameters.length() < 2) {
|
||||
if (parameters.length() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -819,18 +947,20 @@ void TwitchMessageBuilder::appendTwitchEmote(
|
||||
|
||||
auto occurences = parameters.at(1).split(',');
|
||||
|
||||
for (QString occurence : occurences) {
|
||||
for (QString occurence : occurences)
|
||||
{
|
||||
auto coords = occurence.split('-');
|
||||
|
||||
if (coords.length() < 2) {
|
||||
if (coords.length() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto start = coords.at(0).toInt();
|
||||
auto end = coords.at(1).toInt();
|
||||
|
||||
if (start >= end || start < 0 ||
|
||||
end > this->originalMessage_.length()) {
|
||||
if (start >= end || start < 0 || end > this->originalMessage_.length())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -838,7 +968,8 @@ void TwitchMessageBuilder::appendTwitchEmote(
|
||||
EmoteName{this->originalMessage_.mid(start, end - start + 1)};
|
||||
auto tup = std::tuple<int, EmotePtr, EmoteName>{
|
||||
start, app->emotes->twitch.getOrCreateEmote(id, name), name};
|
||||
if (std::get<1>(tup) == nullptr) {
|
||||
if (std::get<1>(tup) == nullptr)
|
||||
{
|
||||
log("nullptr {}", std::get<2>(tup).string);
|
||||
}
|
||||
vec.push_back(std::move(tup));
|
||||
@@ -849,24 +980,33 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
|
||||
{
|
||||
// Special channels, like /whispers and /channels return here
|
||||
// This means they will not render any BTTV or FFZ emotes
|
||||
if (this->twitchChannel == nullptr) {
|
||||
if (this->twitchChannel == nullptr)
|
||||
{
|
||||
return Failure;
|
||||
}
|
||||
|
||||
auto flags = MessageElementFlags();
|
||||
auto emote = boost::optional<EmotePtr>{};
|
||||
|
||||
if ((emote = this->twitchChannel->globalBttv().emote(name))) {
|
||||
if ((emote = this->twitchChannel->globalBttv().emote(name)))
|
||||
{
|
||||
flags = MessageElementFlag::BttvEmote;
|
||||
} else if ((emote = this->twitchChannel->bttvEmote(name))) {
|
||||
}
|
||||
else if ((emote = this->twitchChannel->bttvEmote(name)))
|
||||
{
|
||||
flags = MessageElementFlag::BttvEmote;
|
||||
} else if ((emote = this->twitchChannel->globalFfz().emote(name))) {
|
||||
}
|
||||
else if ((emote = this->twitchChannel->globalFfz().emote(name)))
|
||||
{
|
||||
flags = MessageElementFlag::FfzEmote;
|
||||
} else if ((emote = this->twitchChannel->ffzEmote(name))) {
|
||||
}
|
||||
else if ((emote = this->twitchChannel->ffzEmote(name)))
|
||||
{
|
||||
flags = MessageElementFlag::FfzEmote;
|
||||
}
|
||||
|
||||
if (emote) {
|
||||
if (emote)
|
||||
{
|
||||
this->emplace<EmoteElement>(emote.get(), flags);
|
||||
return Success;
|
||||
}
|
||||
@@ -877,96 +1017,129 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
|
||||
// fourtf: this is ugly
|
||||
void TwitchMessageBuilder::appendTwitchBadges()
|
||||
{
|
||||
if (this->twitchChannel == nullptr) {
|
||||
if (this->twitchChannel == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto app = getApp();
|
||||
|
||||
auto iterator = this->tags.find("badges");
|
||||
if (iterator == this->tags.end()) return;
|
||||
if (iterator == this->tags.end())
|
||||
return;
|
||||
|
||||
for (QString badge : iterator.value().toString().split(',')) {
|
||||
if (badge.startsWith("bits/")) {
|
||||
for (QString badge : iterator.value().toString().split(','))
|
||||
{
|
||||
if (badge.startsWith("bits/"))
|
||||
{
|
||||
QString cheerAmount = badge.mid(5);
|
||||
QString tooltip = QString("Twitch cheer ") + cheerAmount;
|
||||
|
||||
// Try to fetch channel-specific bit badge
|
||||
try {
|
||||
try
|
||||
{
|
||||
if (twitchChannel)
|
||||
if (const auto &badge = this->twitchChannel->twitchBadge(
|
||||
"bits", cheerAmount)) {
|
||||
"bits", cheerAmount))
|
||||
{
|
||||
this->emplace<EmoteElement>(
|
||||
badge.get(), MessageElementFlag::BadgeVanity)
|
||||
->setTooltip(tooltip);
|
||||
continue;
|
||||
}
|
||||
} catch (const std::out_of_range &) {
|
||||
}
|
||||
catch (const std::out_of_range &)
|
||||
{
|
||||
// Channel does not contain a special bit badge for this version
|
||||
}
|
||||
|
||||
// Use default bit badge
|
||||
if (auto badge = this->twitchChannel->globalTwitchBadges().badge(
|
||||
"bits", cheerAmount)) {
|
||||
"bits", cheerAmount))
|
||||
{
|
||||
this->emplace<EmoteElement>(badge.get(),
|
||||
MessageElementFlag::BadgeVanity)
|
||||
->setTooltip(tooltip);
|
||||
}
|
||||
} else if (badge == "staff/1") {
|
||||
}
|
||||
else if (badge == "staff/1")
|
||||
{
|
||||
this->emplace<ImageElement>(
|
||||
Image::fromPixmap(app->resources->twitch.staff),
|
||||
MessageElementFlag::BadgeGlobalAuthority)
|
||||
->setTooltip("Twitch Staff");
|
||||
} else if (badge == "admin/1") {
|
||||
}
|
||||
else if (badge == "admin/1")
|
||||
{
|
||||
this->emplace<ImageElement>(
|
||||
Image::fromPixmap(app->resources->twitch.admin),
|
||||
MessageElementFlag::BadgeGlobalAuthority)
|
||||
->setTooltip("Twitch Admin");
|
||||
} else if (badge == "global_mod/1") {
|
||||
}
|
||||
else if (badge == "global_mod/1")
|
||||
{
|
||||
this->emplace<ImageElement>(
|
||||
Image::fromPixmap(app->resources->twitch.globalmod),
|
||||
MessageElementFlag::BadgeGlobalAuthority)
|
||||
->setTooltip("Twitch Global Moderator");
|
||||
} else if (badge == "moderator/1") {
|
||||
}
|
||||
else if (badge == "moderator/1")
|
||||
{
|
||||
// TODO: Implement custom FFZ moderator badge
|
||||
this->emplace<ImageElement>(
|
||||
Image::fromPixmap(app->resources->twitch.moderator),
|
||||
MessageElementFlag::BadgeChannelAuthority)
|
||||
->setTooltip("Twitch Channel Moderator");
|
||||
} else if (badge == "turbo/1") {
|
||||
}
|
||||
else if (badge == "turbo/1")
|
||||
{
|
||||
this->emplace<ImageElement>(
|
||||
Image::fromPixmap(app->resources->twitch.turbo),
|
||||
MessageElementFlag::BadgeGlobalAuthority)
|
||||
->setTooltip("Twitch Turbo Subscriber");
|
||||
} else if (badge == "broadcaster/1") {
|
||||
}
|
||||
else if (badge == "broadcaster/1")
|
||||
{
|
||||
this->emplace<ImageElement>(
|
||||
Image::fromPixmap(app->resources->twitch.broadcaster),
|
||||
MessageElementFlag::BadgeChannelAuthority)
|
||||
->setTooltip("Twitch Broadcaster");
|
||||
} else if (badge == "premium/1") {
|
||||
}
|
||||
else if (badge == "premium/1")
|
||||
{
|
||||
this->emplace<ImageElement>(
|
||||
Image::fromPixmap(app->resources->twitch.prime),
|
||||
MessageElementFlag::BadgeVanity)
|
||||
->setTooltip("Twitch Prime Subscriber");
|
||||
} else if (badge.startsWith("partner/")) {
|
||||
}
|
||||
else if (badge.startsWith("partner/"))
|
||||
{
|
||||
int index = badge.midRef(8).toInt();
|
||||
switch (index) {
|
||||
case 1: {
|
||||
switch (index)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
this->emplace<ImageElement>(
|
||||
Image::fromPixmap(app->resources->twitch.verified,
|
||||
0.25),
|
||||
MessageElementFlag::BadgeVanity)
|
||||
->setTooltip("Twitch Verified");
|
||||
} break;
|
||||
default: {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
printf("[TwitchMessageBuilder] Unhandled partner badge "
|
||||
"index: %d\n",
|
||||
index);
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (badge.startsWith("subscriber/")) {
|
||||
}
|
||||
else if (badge.startsWith("subscriber/"))
|
||||
{
|
||||
if (auto badgeEmote = this->twitchChannel->twitchBadge(
|
||||
"subscriber", badge.mid(11))) {
|
||||
"subscriber", badge.mid(11)))
|
||||
{
|
||||
this->emplace<EmoteElement>(
|
||||
badgeEmote.get(), MessageElementFlag::BadgeSubscription)
|
||||
->setTooltip((*badgeEmote)->tooltip.string);
|
||||
@@ -978,12 +1151,16 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||
Image::fromPixmap(app->resources->twitch.subscriber, 0.25),
|
||||
MessageElementFlag::BadgeSubscription)
|
||||
->setTooltip("Twitch Subscriber");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
auto splits = badge.split('/');
|
||||
if (splits.size() != 2) continue;
|
||||
if (splits.size() != 2)
|
||||
continue;
|
||||
|
||||
if (auto badgeEmote =
|
||||
this->twitchChannel->twitchBadge(splits[0], splits[1])) {
|
||||
this->twitchChannel->twitchBadge(splits[0], splits[1]))
|
||||
{
|
||||
this->emplace<EmoteElement>(badgeEmote.get(),
|
||||
MessageElementFlag::BadgeVanity)
|
||||
->setTooltip((*badgeEmote)->tooltip.string);
|
||||
@@ -997,7 +1174,8 @@ void TwitchMessageBuilder::appendChatterinoBadges()
|
||||
{
|
||||
auto chatterinoBadgePtr =
|
||||
getApp()->chatterinoBadges->getBadge({this->userName});
|
||||
if (chatterinoBadgePtr) {
|
||||
if (chatterinoBadgePtr)
|
||||
{
|
||||
this->emplace<EmoteElement>(*chatterinoBadgePtr,
|
||||
MessageElementFlag::BadgeChatterino);
|
||||
}
|
||||
|
||||
@@ -12,13 +12,15 @@ namespace {
|
||||
inline bool ReadValue(const rapidjson::Value &object, const char *key,
|
||||
Type &out)
|
||||
{
|
||||
if (!object.HasMember(key)) {
|
||||
if (!object.HasMember(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &value = object[key];
|
||||
|
||||
if (!value.Is<Type>()) {
|
||||
if (!value.Is<Type>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -31,13 +33,15 @@ namespace {
|
||||
inline bool ReadValue<QString>(const rapidjson::Value &object,
|
||||
const char *key, QString &out)
|
||||
{
|
||||
if (!object.HasMember(key)) {
|
||||
if (!object.HasMember(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &value = object[key];
|
||||
|
||||
if (!value.IsString()) {
|
||||
if (!value.IsString())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -51,18 +55,22 @@ namespace {
|
||||
const char *key,
|
||||
std::vector<QString> &out)
|
||||
{
|
||||
if (!object.HasMember(key)) {
|
||||
if (!object.HasMember(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &value = object[key];
|
||||
|
||||
if (!value.IsArray()) {
|
||||
if (!value.IsArray())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const rapidjson::Value &innerValue : value.GetArray()) {
|
||||
if (!innerValue.IsString()) {
|
||||
for (const rapidjson::Value &innerValue : value.GetArray())
|
||||
{
|
||||
if (!innerValue.IsString())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -76,141 +84,173 @@ namespace {
|
||||
inline bool ParseSingleCheermoteSet(JSONCheermoteSet &set,
|
||||
const rapidjson::Value &action)
|
||||
{
|
||||
if (!action.IsObject()) {
|
||||
if (!action.IsObject())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "prefix", set.prefix)) {
|
||||
if (!ReadValue(action, "prefix", set.prefix))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "scales", set.scales)) {
|
||||
if (!ReadValue(action, "scales", set.scales))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "backgrounds", set.backgrounds)) {
|
||||
if (!ReadValue(action, "backgrounds", set.backgrounds))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "states", set.states)) {
|
||||
if (!ReadValue(action, "states", set.states))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "type", set.type)) {
|
||||
if (!ReadValue(action, "type", set.type))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "updated_at", set.updatedAt)) {
|
||||
if (!ReadValue(action, "updated_at", set.updatedAt))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "priority", set.priority)) {
|
||||
if (!ReadValue(action, "priority", set.priority))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tiers
|
||||
if (!action.HasMember("tiers")) {
|
||||
if (!action.HasMember("tiers"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &tiersValue = action["tiers"];
|
||||
|
||||
if (!tiersValue.IsArray()) {
|
||||
if (!tiersValue.IsArray())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const rapidjson::Value &tierValue : tiersValue.GetArray()) {
|
||||
for (const rapidjson::Value &tierValue : tiersValue.GetArray())
|
||||
{
|
||||
JSONCheermoteSet::CheermoteTier tier;
|
||||
|
||||
if (!tierValue.IsObject()) {
|
||||
if (!tierValue.IsObject())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(tierValue, "min_bits", tier.minBits)) {
|
||||
if (!ReadValue(tierValue, "min_bits", tier.minBits))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(tierValue, "id", tier.id)) {
|
||||
if (!ReadValue(tierValue, "id", tier.id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(tierValue, "color", tier.color)) {
|
||||
if (!ReadValue(tierValue, "color", tier.color))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Images
|
||||
if (!tierValue.HasMember("images")) {
|
||||
if (!tierValue.HasMember("images"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &imagesValue = tierValue["images"];
|
||||
|
||||
if (!imagesValue.IsObject()) {
|
||||
if (!imagesValue.IsObject())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read images object
|
||||
for (const auto &imageBackgroundValue : imagesValue.GetObject()) {
|
||||
for (const auto &imageBackgroundValue : imagesValue.GetObject())
|
||||
{
|
||||
QString background = imageBackgroundValue.name.GetString();
|
||||
bool backgroundExists = false;
|
||||
for (const auto &bg : set.backgrounds) {
|
||||
if (background == bg) {
|
||||
for (const auto &bg : set.backgrounds)
|
||||
{
|
||||
if (background == bg)
|
||||
{
|
||||
backgroundExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!backgroundExists) {
|
||||
if (!backgroundExists)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const rapidjson::Value &imageBackgroundStates =
|
||||
imageBackgroundValue.value;
|
||||
if (!imageBackgroundStates.IsObject()) {
|
||||
if (!imageBackgroundStates.IsObject())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read each key which represents a background
|
||||
for (const auto &imageBackgroundState :
|
||||
imageBackgroundStates.GetObject()) {
|
||||
imageBackgroundStates.GetObject())
|
||||
{
|
||||
QString state = imageBackgroundState.name.GetString();
|
||||
bool stateExists = false;
|
||||
for (const auto &_state : set.states) {
|
||||
if (state == _state) {
|
||||
for (const auto &_state : set.states)
|
||||
{
|
||||
if (state == _state)
|
||||
{
|
||||
stateExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!stateExists) {
|
||||
if (!stateExists)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const rapidjson::Value &imageScalesValue =
|
||||
imageBackgroundState.value;
|
||||
if (!imageScalesValue.IsObject()) {
|
||||
if (!imageScalesValue.IsObject())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read each key which represents a scale
|
||||
for (const auto &imageScaleValue :
|
||||
imageScalesValue.GetObject()) {
|
||||
imageScalesValue.GetObject())
|
||||
{
|
||||
QString scale = imageScaleValue.name.GetString();
|
||||
bool scaleExists = false;
|
||||
for (const auto &_scale : set.scales) {
|
||||
if (scale == _scale) {
|
||||
for (const auto &_scale : set.scales)
|
||||
{
|
||||
if (scale == _scale)
|
||||
{
|
||||
scaleExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!scaleExists) {
|
||||
if (!scaleExists)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const rapidjson::Value &imageScaleURLValue =
|
||||
imageScaleValue.value;
|
||||
if (!imageScaleURLValue.IsString()) {
|
||||
if (!imageScaleURLValue.IsString())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -218,7 +258,8 @@ namespace {
|
||||
|
||||
bool ok = false;
|
||||
qreal scaleNumber = scale.toFloat(&ok);
|
||||
if (!ok) {
|
||||
if (!ok)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -246,25 +287,30 @@ std::vector<JSONCheermoteSet> ParseCheermoteSets(const rapidjson::Document &d)
|
||||
{
|
||||
std::vector<JSONCheermoteSet> sets;
|
||||
|
||||
if (!d.IsObject()) {
|
||||
if (!d.IsObject())
|
||||
{
|
||||
return sets;
|
||||
}
|
||||
|
||||
if (!d.HasMember("actions")) {
|
||||
if (!d.HasMember("actions"))
|
||||
{
|
||||
return sets;
|
||||
}
|
||||
|
||||
const auto &actionsValue = d["actions"];
|
||||
|
||||
if (!actionsValue.IsArray()) {
|
||||
if (!actionsValue.IsArray())
|
||||
{
|
||||
return sets;
|
||||
}
|
||||
|
||||
for (const auto &action : actionsValue.GetArray()) {
|
||||
for (const auto &action : actionsValue.GetArray())
|
||||
{
|
||||
JSONCheermoteSet set;
|
||||
bool res = ParseSingleCheermoteSet(set, action);
|
||||
|
||||
if (res) {
|
||||
if (res)
|
||||
{
|
||||
sets.emplace_back(set);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
|
||||
QString username = account->getUserName();
|
||||
QString oauthToken = account->getOAuthToken();
|
||||
|
||||
if (!oauthToken.startsWith("oauth:")) {
|
||||
if (!oauthToken.startsWith("oauth:"))
|
||||
{
|
||||
oauthToken.prepend("oauth:");
|
||||
}
|
||||
|
||||
@@ -66,7 +67,8 @@ void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
|
||||
connection->setNickName(username);
|
||||
connection->setRealName(username);
|
||||
|
||||
if (!account->isAnon()) {
|
||||
if (!account->isAnon())
|
||||
{
|
||||
connection->setPassword(oauthToken);
|
||||
}
|
||||
|
||||
@@ -103,7 +105,8 @@ void TwitchServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
|
||||
void TwitchServer::messageReceived(Communi::IrcMessage *message)
|
||||
{
|
||||
// this->readConnection
|
||||
if (message->type() == Communi::IrcMessage::Type::Private) {
|
||||
if (message->type() == Communi::IrcMessage::Type::Private)
|
||||
{
|
||||
// We already have a handler for private messages
|
||||
return;
|
||||
}
|
||||
@@ -112,35 +115,55 @@ void TwitchServer::messageReceived(Communi::IrcMessage *message)
|
||||
|
||||
auto &handler = IrcMessageHandler::getInstance();
|
||||
|
||||
if (command == "ROOMSTATE") {
|
||||
if (command == "ROOMSTATE")
|
||||
{
|
||||
handler.handleRoomStateMessage(message);
|
||||
} else if (command == "CLEARCHAT") {
|
||||
}
|
||||
else if (command == "CLEARCHAT")
|
||||
{
|
||||
handler.handleClearChatMessage(message);
|
||||
} else if (command == "USERSTATE") {
|
||||
}
|
||||
else if (command == "USERSTATE")
|
||||
{
|
||||
handler.handleUserStateMessage(message);
|
||||
} else if (command == "WHISPER") {
|
||||
}
|
||||
else if (command == "WHISPER")
|
||||
{
|
||||
handler.handleWhisperMessage(message);
|
||||
} else if (command == "USERNOTICE") {
|
||||
}
|
||||
else if (command == "USERNOTICE")
|
||||
{
|
||||
handler.handleUserNoticeMessage(message, *this);
|
||||
} else if (command == "MODE") {
|
||||
}
|
||||
else if (command == "MODE")
|
||||
{
|
||||
handler.handleModeMessage(message);
|
||||
} else if (command == "NOTICE") {
|
||||
}
|
||||
else if (command == "NOTICE")
|
||||
{
|
||||
handler.handleNoticeMessage(
|
||||
static_cast<Communi::IrcNoticeMessage *>(message));
|
||||
} else if (command == "JOIN") {
|
||||
}
|
||||
else if (command == "JOIN")
|
||||
{
|
||||
handler.handleJoinMessage(message);
|
||||
} else if (command == "PART") {
|
||||
}
|
||||
else if (command == "PART")
|
||||
{
|
||||
handler.handlePartMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
|
||||
{
|
||||
switch (message->type()) {
|
||||
case Communi::IrcMessage::Type::Notice: {
|
||||
switch (message->type())
|
||||
{
|
||||
case Communi::IrcMessage::Type::Notice:
|
||||
{
|
||||
IrcMessageHandler::getInstance().handleWriteConnectionNoticeMessage(
|
||||
static_cast<Communi::IrcNoticeMessage *>(message));
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
@@ -149,11 +172,13 @@ void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
|
||||
std::shared_ptr<Channel> TwitchServer::getCustomChannel(
|
||||
const QString &channelName)
|
||||
{
|
||||
if (channelName == "/whispers") {
|
||||
if (channelName == "/whispers")
|
||||
{
|
||||
return this->whispersChannel;
|
||||
}
|
||||
|
||||
if (channelName == "/mentions") {
|
||||
if (channelName == "/mentions")
|
||||
{
|
||||
return this->mentionsChannel;
|
||||
}
|
||||
|
||||
@@ -174,14 +199,18 @@ std::shared_ptr<Channel> TwitchServer::getChannelOrEmptyByID(
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
for (const auto &weakChannel : this->channels) {
|
||||
for (const auto &weakChannel : this->channels)
|
||||
{
|
||||
auto channel = weakChannel.lock();
|
||||
if (!channel) continue;
|
||||
if (!channel)
|
||||
continue;
|
||||
|
||||
auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel);
|
||||
if (!twitchChannel) continue;
|
||||
if (!twitchChannel)
|
||||
continue;
|
||||
|
||||
if (twitchChannel->roomId() == channelId) {
|
||||
if (twitchChannel->roomId() == channelId)
|
||||
{
|
||||
return twitchChannel;
|
||||
}
|
||||
}
|
||||
@@ -217,9 +246,10 @@ void TwitchServer::onMessageSendRequested(TwitchChannel *channel,
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
// check if you are sending messages too fast
|
||||
if (!lastMessage.empty() &&
|
||||
lastMessage.back() + minMessageOffset > now) {
|
||||
if (this->lastErrorTimeSpeed_ + 30s < now) {
|
||||
if (!lastMessage.empty() && lastMessage.back() + minMessageOffset > now)
|
||||
{
|
||||
if (this->lastErrorTimeSpeed_ + 30s < now)
|
||||
{
|
||||
auto errorMessage =
|
||||
makeSystemMessage("sending messages too fast");
|
||||
|
||||
@@ -231,13 +261,16 @@ void TwitchServer::onMessageSendRequested(TwitchChannel *channel,
|
||||
}
|
||||
|
||||
// remove messages older than 30 seconds
|
||||
while (!lastMessage.empty() && lastMessage.front() + 32s < now) {
|
||||
while (!lastMessage.empty() && lastMessage.front() + 32s < now)
|
||||
{
|
||||
lastMessage.pop();
|
||||
}
|
||||
|
||||
// check if you are sending too many messages
|
||||
if (lastMessage.size() >= maxMessageCount) {
|
||||
if (this->lastErrorTimeAmount_ + 30s < now) {
|
||||
if (lastMessage.size() >= maxMessageCount)
|
||||
{
|
||||
if (this->lastErrorTimeAmount_ + 30s < now)
|
||||
{
|
||||
auto errorMessage =
|
||||
makeSystemMessage("sending too many messages");
|
||||
|
||||
|
||||
@@ -43,26 +43,30 @@ namespace Settings {
|
||||
|
||||
TwitchUser user;
|
||||
|
||||
if (!value.IsObject()) {
|
||||
if (!value.IsObject())
|
||||
{
|
||||
PAJLADA_REPORT_ERROR(error)
|
||||
PAJLADA_THROW_EXCEPTION(
|
||||
"Deserialized rapidjson::Value is wrong type");
|
||||
return user;
|
||||
}
|
||||
|
||||
if (!rj::getSafe(value, "_id", user.id)) {
|
||||
if (!rj::getSafe(value, "_id", user.id))
|
||||
{
|
||||
PAJLADA_REPORT_ERROR(error)
|
||||
PAJLADA_THROW_EXCEPTION("Missing ID key");
|
||||
return user;
|
||||
}
|
||||
|
||||
if (!rj::getSafe(value, "name", user.name)) {
|
||||
if (!rj::getSafe(value, "name", user.name))
|
||||
{
|
||||
PAJLADA_REPORT_ERROR(error)
|
||||
PAJLADA_THROW_EXCEPTION("Missing name key");
|
||||
return user;
|
||||
}
|
||||
|
||||
if (!rj::getSafe(value, "display_name", user.displayName)) {
|
||||
if (!rj::getSafe(value, "display_name", user.displayName))
|
||||
{
|
||||
PAJLADA_REPORT_ERROR(error)
|
||||
PAJLADA_THROW_EXCEPTION("Missing display name key");
|
||||
return user;
|
||||
|
||||
Reference in New Issue
Block a user