changed .clang-format
This commit is contained in:
@@ -24,157 +24,159 @@ static std::map<QString, std::string> sentMessages;
|
||||
|
||||
namespace detail {
|
||||
|
||||
PubSubClient::PubSubClient(WebsocketClient &websocketClient,
|
||||
WebsocketHandle handle)
|
||||
: websocketClient_(websocketClient)
|
||||
, handle_(handle)
|
||||
{
|
||||
}
|
||||
|
||||
void PubSubClient::start()
|
||||
{
|
||||
assert(!this->started_);
|
||||
|
||||
this->started_ = true;
|
||||
|
||||
this->ping();
|
||||
}
|
||||
|
||||
void PubSubClient::stop()
|
||||
{
|
||||
assert(this->started_);
|
||||
|
||||
this->started_ = false;
|
||||
}
|
||||
|
||||
bool PubSubClient::listen(rapidjson::Document &message)
|
||||
{
|
||||
int numRequestedListens = message["data"]["topics"].Size();
|
||||
|
||||
if (this->numListens_ + numRequestedListens > MAX_PUBSUB_LISTENS) {
|
||||
// This PubSubClient is already at its peak listens
|
||||
return false;
|
||||
PubSubClient::PubSubClient(WebsocketClient &websocketClient,
|
||||
WebsocketHandle handle)
|
||||
: websocketClient_(websocketClient)
|
||||
, handle_(handle)
|
||||
{
|
||||
}
|
||||
|
||||
this->numListens_ += numRequestedListens;
|
||||
void PubSubClient::start()
|
||||
{
|
||||
assert(!this->started_);
|
||||
|
||||
for (const auto &topic : message["data"]["topics"].GetArray()) {
|
||||
this->listeners_.emplace_back(
|
||||
Listener{topic.GetString(), false, false, false});
|
||||
this->started_ = true;
|
||||
|
||||
this->ping();
|
||||
}
|
||||
|
||||
auto uuid = CreateUUID();
|
||||
void PubSubClient::stop()
|
||||
{
|
||||
assert(this->started_);
|
||||
|
||||
rj::set(message, "nonce", uuid);
|
||||
this->started_ = false;
|
||||
}
|
||||
|
||||
std::string payload = rj::stringify(message);
|
||||
sentMessages[uuid] = payload;
|
||||
bool PubSubClient::listen(rapidjson::Document &message)
|
||||
{
|
||||
int numRequestedListens = message["data"]["topics"].Size();
|
||||
|
||||
this->send(payload.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PubSubClient::unlistenPrefix(const std::string &prefix)
|
||||
{
|
||||
std::vector<std::string> topics;
|
||||
|
||||
for (auto it = this->listeners_.begin(); it != this->listeners_.end();) {
|
||||
const auto &listener = *it;
|
||||
if (listener.topic.find(prefix) == 0) {
|
||||
topics.push_back(listener.topic);
|
||||
it = this->listeners_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
if (this->numListens_ + numRequestedListens > MAX_PUBSUB_LISTENS) {
|
||||
// This PubSubClient is already at its peak listens
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (topics.empty()) {
|
||||
return;
|
||||
}
|
||||
this->numListens_ += numRequestedListens;
|
||||
|
||||
auto message = createUnlistenMessage(topics);
|
||||
|
||||
auto uuid = CreateUUID();
|
||||
|
||||
rj::set(message, "nonce", CreateUUID());
|
||||
|
||||
std::string payload = rj::stringify(message);
|
||||
sentMessages[uuid] = payload;
|
||||
|
||||
this->send(payload.c_str());
|
||||
}
|
||||
|
||||
void PubSubClient::handlePong()
|
||||
{
|
||||
assert(this->awaitingPong_);
|
||||
|
||||
log("Got pong!");
|
||||
|
||||
this->awaitingPong_ = false;
|
||||
}
|
||||
|
||||
bool PubSubClient::isListeningToTopic(const std::string &payload)
|
||||
{
|
||||
for (const auto &listener : this->listeners_) {
|
||||
if (listener.topic == payload) {
|
||||
return true;
|
||||
for (const auto &topic : message["data"]["topics"].GetArray()) {
|
||||
this->listeners_.emplace_back(
|
||||
Listener{topic.GetString(), false, false, false});
|
||||
}
|
||||
|
||||
auto uuid = CreateUUID();
|
||||
|
||||
rj::set(message, "nonce", uuid);
|
||||
|
||||
std::string payload = rj::stringify(message);
|
||||
sentMessages[uuid] = payload;
|
||||
|
||||
this->send(payload.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
void PubSubClient::unlistenPrefix(const std::string &prefix)
|
||||
{
|
||||
std::vector<std::string> topics;
|
||||
|
||||
void PubSubClient::ping()
|
||||
{
|
||||
assert(this->started_);
|
||||
for (auto it = this->listeners_.begin();
|
||||
it != this->listeners_.end();) {
|
||||
const auto &listener = *it;
|
||||
if (listener.topic.find(prefix) == 0) {
|
||||
topics.push_back(listener.topic);
|
||||
it = this->listeners_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->send(pingPayload)) {
|
||||
return;
|
||||
if (topics.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto message = createUnlistenMessage(topics);
|
||||
|
||||
auto uuid = CreateUUID();
|
||||
|
||||
rj::set(message, "nonce", CreateUUID());
|
||||
|
||||
std::string payload = rj::stringify(message);
|
||||
sentMessages[uuid] = payload;
|
||||
|
||||
this->send(payload.c_str());
|
||||
}
|
||||
|
||||
this->awaitingPong_ = true;
|
||||
void PubSubClient::handlePong()
|
||||
{
|
||||
assert(this->awaitingPong_);
|
||||
|
||||
auto self = this->shared_from_this();
|
||||
log("Got pong!");
|
||||
|
||||
runAfter(this->websocketClient_.get_io_service(), std::chrono::seconds(15),
|
||||
[self](auto timer) {
|
||||
if (!self->started_) {
|
||||
return;
|
||||
}
|
||||
this->awaitingPong_ = false;
|
||||
}
|
||||
|
||||
if (self->awaitingPong_) {
|
||||
log("No pong respnose, disconnect!");
|
||||
// TODO(pajlada): Label this connection as "disconnect me"
|
||||
}
|
||||
});
|
||||
|
||||
runAfter(this->websocketClient_.get_io_service(), std::chrono::minutes(5),
|
||||
[self](auto timer) {
|
||||
if (!self->started_) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->ping(); //
|
||||
});
|
||||
}
|
||||
|
||||
bool PubSubClient::send(const char *payload)
|
||||
{
|
||||
WebsocketErrorCode ec;
|
||||
this->websocketClient_.send(this->handle_, payload,
|
||||
websocketpp::frame::opcode::text, ec);
|
||||
|
||||
if (ec) {
|
||||
log("Error sending message {}: {}", payload, ec.message());
|
||||
// TODO(pajlada): Check which error code happened and maybe gracefully
|
||||
// handle it
|
||||
bool PubSubClient::isListeningToTopic(const std::string &payload)
|
||||
{
|
||||
for (const auto &listener : this->listeners_) {
|
||||
if (listener.topic == payload) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void PubSubClient::ping()
|
||||
{
|
||||
assert(this->started_);
|
||||
|
||||
if (!this->send(pingPayload)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->awaitingPong_ = true;
|
||||
|
||||
auto self = this->shared_from_this();
|
||||
|
||||
runAfter(this->websocketClient_.get_io_service(),
|
||||
std::chrono::seconds(15), [self](auto timer) {
|
||||
if (!self->started_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->awaitingPong_) {
|
||||
log("No pong respnose, disconnect!");
|
||||
// TODO(pajlada): Label this connection as "disconnect
|
||||
// me"
|
||||
}
|
||||
});
|
||||
|
||||
runAfter(this->websocketClient_.get_io_service(),
|
||||
std::chrono::minutes(5), [self](auto timer) {
|
||||
if (!self->started_) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->ping(); //
|
||||
});
|
||||
}
|
||||
|
||||
bool PubSubClient::send(const char *payload)
|
||||
{
|
||||
WebsocketErrorCode ec;
|
||||
this->websocketClient_.send(this->handle_, payload,
|
||||
websocketpp::frame::opcode::text, ec);
|
||||
|
||||
if (ec) {
|
||||
log("Error sending message {}: {}", payload, ec.message());
|
||||
// TODO(pajlada): Check which error code happened and maybe
|
||||
// gracefully handle it
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
|
||||
@@ -32,41 +32,42 @@ using WebsocketErrorCode = websocketpp::lib::error_code;
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct Listener {
|
||||
std::string topic;
|
||||
bool authed;
|
||||
bool persistent;
|
||||
bool confirmed = false;
|
||||
};
|
||||
struct Listener {
|
||||
std::string topic;
|
||||
bool authed;
|
||||
bool persistent;
|
||||
bool confirmed = false;
|
||||
};
|
||||
|
||||
class PubSubClient : public std::enable_shared_from_this<PubSubClient>
|
||||
{
|
||||
public:
|
||||
PubSubClient(WebsocketClient &_websocketClient, WebsocketHandle _handle);
|
||||
class PubSubClient : public std::enable_shared_from_this<PubSubClient>
|
||||
{
|
||||
public:
|
||||
PubSubClient(WebsocketClient &_websocketClient,
|
||||
WebsocketHandle _handle);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
bool listen(rapidjson::Document &message);
|
||||
void unlistenPrefix(const std::string &prefix);
|
||||
bool listen(rapidjson::Document &message);
|
||||
void unlistenPrefix(const std::string &prefix);
|
||||
|
||||
void handlePong();
|
||||
void handlePong();
|
||||
|
||||
bool isListeningToTopic(const std::string &topic);
|
||||
bool isListeningToTopic(const std::string &topic);
|
||||
|
||||
private:
|
||||
void ping();
|
||||
bool send(const char *payload);
|
||||
private:
|
||||
void ping();
|
||||
bool send(const char *payload);
|
||||
|
||||
WebsocketClient &websocketClient_;
|
||||
WebsocketHandle handle_;
|
||||
uint16_t numListens_ = 0;
|
||||
WebsocketClient &websocketClient_;
|
||||
WebsocketHandle handle_;
|
||||
uint16_t numListens_ = 0;
|
||||
|
||||
std::vector<Listener> listeners_;
|
||||
std::vector<Listener> listeners_;
|
||||
|
||||
std::atomic<bool> awaitingPong_{false};
|
||||
std::atomic<bool> started_{false};
|
||||
};
|
||||
std::atomic<bool> awaitingPong_{false};
|
||||
std::atomic<bool> started_{false};
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "debug/Log.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <memory>
|
||||
#include "debug/Log.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
|
||||
@@ -15,32 +15,32 @@ namespace chatterino {
|
||||
|
||||
namespace {
|
||||
|
||||
EmoteName cleanUpCode(const EmoteName &dirtyEmoteCode)
|
||||
{
|
||||
auto cleanCode = dirtyEmoteCode.string;
|
||||
cleanCode.detach();
|
||||
EmoteName cleanUpCode(const EmoteName &dirtyEmoteCode)
|
||||
{
|
||||
auto cleanCode = dirtyEmoteCode.string;
|
||||
cleanCode.detach();
|
||||
|
||||
static QMap<QString, QString> emoteNameReplacements{
|
||||
{"[oO](_|\\.)[oO]", "O_o"}, {"\\>\\;\\(", ">("},
|
||||
{"\\<\\;3", "<3"}, {"\\:-?(o|O)", ":O"},
|
||||
{"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"},
|
||||
{"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("},
|
||||
{"\\:-?\\)", ":)"}, {"\\:-?D", ":D"},
|
||||
{"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"},
|
||||
{"R-?\\)", "R)"}, {"B-?\\)", "B)"},
|
||||
};
|
||||
static QMap<QString, QString> emoteNameReplacements{
|
||||
{"[oO](_|\\.)[oO]", "O_o"}, {"\\>\\;\\(", ">("},
|
||||
{"\\<\\;3", "<3"}, {"\\:-?(o|O)", ":O"},
|
||||
{"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"},
|
||||
{"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("},
|
||||
{"\\:-?\\)", ":)"}, {"\\:-?D", ":D"},
|
||||
{"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"},
|
||||
{"R-?\\)", "R)"}, {"B-?\\)", "B)"},
|
||||
};
|
||||
|
||||
auto it = emoteNameReplacements.find(dirtyEmoteCode.string);
|
||||
if (it != emoteNameReplacements.end()) {
|
||||
cleanCode = it.value();
|
||||
auto it = emoteNameReplacements.find(dirtyEmoteCode.string);
|
||||
if (it != emoteNameReplacements.end()) {
|
||||
cleanCode = it.value();
|
||||
}
|
||||
|
||||
cleanCode.replace("<", "<");
|
||||
cleanCode.replace(">", ">");
|
||||
|
||||
return {cleanCode};
|
||||
}
|
||||
|
||||
cleanCode.replace("<", "<");
|
||||
cleanCode.replace(">", ">");
|
||||
|
||||
return {cleanCode};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TwitchAccount::TwitchAccount(const QString &username, const QString &oauthToken,
|
||||
@@ -417,7 +417,7 @@ void TwitchAccount::loadEmotes()
|
||||
}
|
||||
|
||||
AccessGuard<const TwitchAccount::TwitchAccountEmoteData>
|
||||
TwitchAccount::accessEmotes() const
|
||||
TwitchAccount::accessEmotes() const
|
||||
{
|
||||
return this->emotes_.accessConst();
|
||||
}
|
||||
|
||||
@@ -25,48 +25,49 @@
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
auto parseRecentMessages(const QJsonObject &jsonRoot, TwitchChannel &channel)
|
||||
{
|
||||
QJsonArray jsonMessages = jsonRoot.value("messages").toArray();
|
||||
std::vector<MessagePtr> messages;
|
||||
auto parseRecentMessages(const QJsonObject &jsonRoot,
|
||||
TwitchChannel &channel)
|
||||
{
|
||||
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) {
|
||||
auto content = jsonMessage.toString().toUtf8();
|
||||
// passing nullptr as the channel makes the message invalid but we don't
|
||||
// check for that anyways
|
||||
auto message = Communi::IrcMessage::fromData(content, nullptr);
|
||||
auto privMsg = dynamic_cast<Communi::IrcPrivateMessage *>(message);
|
||||
assert(privMsg);
|
||||
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
|
||||
auto message = Communi::IrcMessage::fromData(content, nullptr);
|
||||
auto privMsg = dynamic_cast<Communi::IrcPrivateMessage *>(message);
|
||||
assert(privMsg);
|
||||
|
||||
MessageParseArgs args;
|
||||
TwitchMessageBuilder builder(&channel, privMsg, args);
|
||||
if (!builder.isIgnored()) {
|
||||
messages.push_back(builder.build());
|
||||
MessageParseArgs args;
|
||||
TwitchMessageBuilder builder(&channel, privMsg, args);
|
||||
if (!builder.isIgnored()) {
|
||||
messages.push_back(builder.build());
|
||||
}
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
std::pair<Outcome, UsernameSet> parseChatters(const QJsonObject &jsonRoot)
|
||||
{
|
||||
static QStringList categories = {"moderators", "staff", "admins",
|
||||
"global_mods", "viewers"};
|
||||
|
||||
return messages;
|
||||
}
|
||||
std::pair<Outcome, UsernameSet> parseChatters(const QJsonObject &jsonRoot)
|
||||
{
|
||||
static QStringList categories = {"moderators", "staff", "admins",
|
||||
"global_mods", "viewers"};
|
||||
auto usernames = UsernameSet();
|
||||
|
||||
auto usernames = UsernameSet();
|
||||
// parse json
|
||||
QJsonObject jsonCategories = jsonRoot.value("chatters").toObject();
|
||||
|
||||
// parse json
|
||||
QJsonObject jsonCategories = jsonRoot.value("chatters").toObject();
|
||||
|
||||
for (const auto &category : categories) {
|
||||
for (auto jsonCategory : jsonCategories.value(category).toArray()) {
|
||||
usernames.insert(jsonCategory.toString());
|
||||
for (const auto &category : categories) {
|
||||
for (auto jsonCategory : jsonCategories.value(category).toArray()) {
|
||||
usernames.insert(jsonCategory.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {Success, std::move(usernames)};
|
||||
}
|
||||
return {Success, std::move(usernames)};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TwitchChannel::TwitchChannel(const QString &name,
|
||||
@@ -309,7 +310,7 @@ bool TwitchChannel::isLive() const
|
||||
}
|
||||
|
||||
AccessGuard<const TwitchChannel::StreamStatus>
|
||||
TwitchChannel::accessStreamStatus() const
|
||||
TwitchChannel::accessStreamStatus() const
|
||||
{
|
||||
return this->streamStatus_.accessConst();
|
||||
}
|
||||
|
||||
@@ -8,235 +8,235 @@ namespace chatterino {
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename Type>
|
||||
inline bool ReadValue(const rapidjson::Value &object, const char *key,
|
||||
Type &out)
|
||||
{
|
||||
if (!object.HasMember(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &value = object[key];
|
||||
|
||||
if (!value.Is<Type>()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out = value.Get<Type>();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool ReadValue<QString>(const rapidjson::Value &object, const char *key,
|
||||
QString &out)
|
||||
{
|
||||
if (!object.HasMember(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &value = object[key];
|
||||
|
||||
if (!value.IsString()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out = value.GetString();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool ReadValue<std::vector<QString>>(const rapidjson::Value &object,
|
||||
const char *key,
|
||||
std::vector<QString> &out)
|
||||
{
|
||||
if (!object.HasMember(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &value = object[key];
|
||||
|
||||
if (!value.IsArray()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const rapidjson::Value &innerValue : value.GetArray()) {
|
||||
if (!innerValue.IsString()) {
|
||||
template <typename Type>
|
||||
inline bool ReadValue(const rapidjson::Value &object, const char *key,
|
||||
Type &out)
|
||||
{
|
||||
if (!object.HasMember(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out.emplace_back(innerValue.GetString());
|
||||
}
|
||||
const auto &value = object[key];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse a single cheermote set (or "action") from the twitch api
|
||||
inline bool ParseSingleCheermoteSet(JSONCheermoteSet &set,
|
||||
const rapidjson::Value &action)
|
||||
{
|
||||
if (!action.IsObject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "prefix", set.prefix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "scales", set.scales)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "backgrounds", set.backgrounds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "states", set.states)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "type", set.type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "updated_at", set.updatedAt)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "priority", set.priority)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tiers
|
||||
if (!action.HasMember("tiers")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &tiersValue = action["tiers"];
|
||||
|
||||
if (!tiersValue.IsArray()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const rapidjson::Value &tierValue : tiersValue.GetArray()) {
|
||||
JSONCheermoteSet::CheermoteTier tier;
|
||||
|
||||
if (!tierValue.IsObject()) {
|
||||
if (!value.Is<Type>()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(tierValue, "min_bits", tier.minBits)) {
|
||||
out = value.Get<Type>();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool ReadValue<QString>(const rapidjson::Value &object,
|
||||
const char *key, QString &out)
|
||||
{
|
||||
if (!object.HasMember(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(tierValue, "id", tier.id)) {
|
||||
const auto &value = object[key];
|
||||
|
||||
if (!value.IsString()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(tierValue, "color", tier.color)) {
|
||||
out = value.GetString();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool ReadValue<std::vector<QString>>(const rapidjson::Value &object,
|
||||
const char *key,
|
||||
std::vector<QString> &out)
|
||||
{
|
||||
if (!object.HasMember(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Images
|
||||
if (!tierValue.HasMember("images")) {
|
||||
const auto &value = object[key];
|
||||
|
||||
if (!value.IsArray()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &imagesValue = tierValue["images"];
|
||||
|
||||
if (!imagesValue.IsObject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read images object
|
||||
for (const auto &imageBackgroundValue : imagesValue.GetObject()) {
|
||||
QString background = imageBackgroundValue.name.GetString();
|
||||
bool backgroundExists = false;
|
||||
for (const auto &bg : set.backgrounds) {
|
||||
if (background == bg) {
|
||||
backgroundExists = true;
|
||||
break;
|
||||
}
|
||||
for (const rapidjson::Value &innerValue : value.GetArray()) {
|
||||
if (!innerValue.IsString()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!backgroundExists) {
|
||||
continue;
|
||||
out.emplace_back(innerValue.GetString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse a single cheermote set (or "action") from the twitch api
|
||||
inline bool ParseSingleCheermoteSet(JSONCheermoteSet &set,
|
||||
const rapidjson::Value &action)
|
||||
{
|
||||
if (!action.IsObject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "prefix", set.prefix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "scales", set.scales)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "backgrounds", set.backgrounds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "states", set.states)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "type", set.type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "updated_at", set.updatedAt)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(action, "priority", set.priority)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tiers
|
||||
if (!action.HasMember("tiers")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &tiersValue = action["tiers"];
|
||||
|
||||
if (!tiersValue.IsArray()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const rapidjson::Value &tierValue : tiersValue.GetArray()) {
|
||||
JSONCheermoteSet::CheermoteTier tier;
|
||||
|
||||
if (!tierValue.IsObject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rapidjson::Value &imageBackgroundStates =
|
||||
imageBackgroundValue.value;
|
||||
if (!imageBackgroundStates.IsObject()) {
|
||||
continue;
|
||||
if (!ReadValue(tierValue, "min_bits", tier.minBits)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read each key which represents a background
|
||||
for (const auto &imageBackgroundState :
|
||||
imageBackgroundStates.GetObject()) {
|
||||
QString state = imageBackgroundState.name.GetString();
|
||||
bool stateExists = false;
|
||||
for (const auto &_state : set.states) {
|
||||
if (state == _state) {
|
||||
stateExists = true;
|
||||
if (!ReadValue(tierValue, "id", tier.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(tierValue, "color", tier.color)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Images
|
||||
if (!tierValue.HasMember("images")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &imagesValue = tierValue["images"];
|
||||
|
||||
if (!imagesValue.IsObject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read images object
|
||||
for (const auto &imageBackgroundValue : imagesValue.GetObject()) {
|
||||
QString background = imageBackgroundValue.name.GetString();
|
||||
bool backgroundExists = false;
|
||||
for (const auto &bg : set.backgrounds) {
|
||||
if (background == bg) {
|
||||
backgroundExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!stateExists) {
|
||||
if (!backgroundExists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const rapidjson::Value &imageScalesValue =
|
||||
imageBackgroundState.value;
|
||||
if (!imageScalesValue.IsObject()) {
|
||||
const rapidjson::Value &imageBackgroundStates =
|
||||
imageBackgroundValue.value;
|
||||
if (!imageBackgroundStates.IsObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read each key which represents a scale
|
||||
for (const auto &imageScaleValue :
|
||||
imageScalesValue.GetObject()) {
|
||||
QString scale = imageScaleValue.name.GetString();
|
||||
bool scaleExists = false;
|
||||
for (const auto &_scale : set.scales) {
|
||||
if (scale == _scale) {
|
||||
scaleExists = true;
|
||||
// Read each key which represents a background
|
||||
for (const auto &imageBackgroundState :
|
||||
imageBackgroundStates.GetObject()) {
|
||||
QString state = imageBackgroundState.name.GetString();
|
||||
bool stateExists = false;
|
||||
for (const auto &_state : set.states) {
|
||||
if (state == _state) {
|
||||
stateExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!scaleExists) {
|
||||
if (!stateExists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const rapidjson::Value &imageScaleURLValue =
|
||||
imageScaleValue.value;
|
||||
if (!imageScaleURLValue.IsString()) {
|
||||
const rapidjson::Value &imageScalesValue =
|
||||
imageBackgroundState.value;
|
||||
if (!imageScalesValue.IsObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString url = imageScaleURLValue.GetString();
|
||||
// Read each key which represents a scale
|
||||
for (const auto &imageScaleValue :
|
||||
imageScalesValue.GetObject()) {
|
||||
QString scale = imageScaleValue.name.GetString();
|
||||
bool scaleExists = false;
|
||||
for (const auto &_scale : set.scales) {
|
||||
if (scale == _scale) {
|
||||
scaleExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
qreal scaleNumber = scale.toFloat(&ok);
|
||||
if (!ok) {
|
||||
continue;
|
||||
if (!scaleExists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const rapidjson::Value &imageScaleURLValue =
|
||||
imageScaleValue.value;
|
||||
if (!imageScaleURLValue.IsString()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString url = imageScaleURLValue.GetString();
|
||||
|
||||
bool ok = false;
|
||||
qreal scaleNumber = scale.toFloat(&ok);
|
||||
if (!ok) {
|
||||
continue;
|
||||
}
|
||||
|
||||
qreal chatterinoScale = 1 / scaleNumber;
|
||||
|
||||
auto image = Image::fromUrl({url}, chatterinoScale);
|
||||
|
||||
// TODO(pajlada): Fill in name and tooltip
|
||||
tier.images[background][state][scale] = image;
|
||||
}
|
||||
|
||||
qreal chatterinoScale = 1 / scaleNumber;
|
||||
|
||||
auto image = Image::fromUrl({url}, chatterinoScale);
|
||||
|
||||
// TODO(pajlada): Fill in name and tooltip
|
||||
tier.images[background][state][scale] = image;
|
||||
}
|
||||
}
|
||||
|
||||
set.tiers.emplace_back(tier);
|
||||
}
|
||||
|
||||
set.tiers.emplace_back(tier);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Look through the results of
|
||||
|
||||
@@ -34,43 +34,43 @@ struct TwitchUser {
|
||||
namespace pajlada {
|
||||
namespace Settings {
|
||||
|
||||
template <>
|
||||
struct Deserialize<chatterino::TwitchUser> {
|
||||
static chatterino::TwitchUser get(const rapidjson::Value &value,
|
||||
bool *error = nullptr)
|
||||
{
|
||||
using namespace chatterino;
|
||||
template <>
|
||||
struct Deserialize<chatterino::TwitchUser> {
|
||||
static chatterino::TwitchUser get(const rapidjson::Value &value,
|
||||
bool *error = nullptr)
|
||||
{
|
||||
using namespace chatterino;
|
||||
|
||||
TwitchUser user;
|
||||
TwitchUser user;
|
||||
|
||||
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)) {
|
||||
PAJLADA_REPORT_ERROR(error)
|
||||
PAJLADA_THROW_EXCEPTION("Missing ID key");
|
||||
return user;
|
||||
}
|
||||
|
||||
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)) {
|
||||
PAJLADA_REPORT_ERROR(error)
|
||||
PAJLADA_THROW_EXCEPTION("Missing display name key");
|
||||
return user;
|
||||
}
|
||||
|
||||
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)) {
|
||||
PAJLADA_REPORT_ERROR(error)
|
||||
PAJLADA_THROW_EXCEPTION("Missing ID key");
|
||||
return user;
|
||||
}
|
||||
|
||||
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)) {
|
||||
PAJLADA_REPORT_ERROR(error)
|
||||
PAJLADA_THROW_EXCEPTION("Missing display name key");
|
||||
return user;
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace Settings
|
||||
} // namespace pajlada
|
||||
|
||||
Reference in New Issue
Block a user