changed .clang-format
This commit is contained in:
@@ -13,73 +13,76 @@
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
|
||||
const QString &emoteScale)
|
||||
{
|
||||
urlTemplate.detach();
|
||||
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
|
||||
const QString &emoteScale)
|
||||
{
|
||||
urlTemplate.detach();
|
||||
|
||||
return {urlTemplate.replace("{{id}}", id.string)
|
||||
.replace("{{image}}", emoteScale)};
|
||||
}
|
||||
std::pair<Outcome, EmoteMap> parseGlobalEmotes(const QJsonObject &jsonRoot,
|
||||
const EmoteMap ¤tEmotes)
|
||||
{
|
||||
auto emotes = EmoteMap();
|
||||
auto jsonEmotes = jsonRoot.value("emotes").toArray();
|
||||
auto urlTemplate = qS("https:") + jsonRoot.value("urlTemplate").toString();
|
||||
|
||||
for (auto jsonEmote : jsonEmotes) {
|
||||
auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
|
||||
auto name = EmoteName{jsonEmote.toObject().value("code").toString()};
|
||||
|
||||
auto emote = Emote(
|
||||
{name,
|
||||
ImageSet{
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
|
||||
Tooltip{name.string + "<br />Global Bttv Emote"},
|
||||
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
||||
|
||||
emotes[name] = cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
|
||||
return {urlTemplate.replace("{{id}}", id.string)
|
||||
.replace("{{image}}", emoteScale)};
|
||||
}
|
||||
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
|
||||
const QJsonObject &jsonRoot, const EmoteMap ¤tEmotes)
|
||||
{
|
||||
auto emotes = EmoteMap();
|
||||
auto jsonEmotes = jsonRoot.value("emotes").toArray();
|
||||
auto urlTemplate =
|
||||
qS("https:") + jsonRoot.value("urlTemplate").toString();
|
||||
|
||||
return {Success, std::move(emotes)};
|
||||
}
|
||||
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
|
||||
{
|
||||
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
|
||||
static std::mutex mutex;
|
||||
for (auto jsonEmote : jsonEmotes) {
|
||||
auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
|
||||
auto name =
|
||||
EmoteName{jsonEmote.toObject().value("code").toString()};
|
||||
|
||||
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
|
||||
}
|
||||
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
|
||||
{
|
||||
auto emotes = EmoteMap();
|
||||
auto jsonEmotes = jsonRoot.value("emotes").toArray();
|
||||
auto urlTemplate = "https:" + jsonRoot.value("urlTemplate").toString();
|
||||
auto emote = Emote(
|
||||
{name,
|
||||
ImageSet{
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
|
||||
Tooltip{name.string + "<br />Global Bttv Emote"},
|
||||
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
||||
|
||||
for (auto jsonEmote_ : jsonEmotes) {
|
||||
auto jsonEmote = jsonEmote_.toObject();
|
||||
emotes[name] =
|
||||
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
|
||||
}
|
||||
|
||||
auto id = EmoteId{jsonEmote.value("id").toString()};
|
||||
auto name = EmoteName{jsonEmote.value("code").toString()};
|
||||
// emoteObject.value("imageType").toString();
|
||||
|
||||
auto emote = Emote(
|
||||
{name,
|
||||
ImageSet{
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
|
||||
Tooltip{name.string + "<br />Channel Bttv Emote"},
|
||||
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
||||
|
||||
emotes[name] = cachedOrMake(std::move(emote), id);
|
||||
return {Success, std::move(emotes)};
|
||||
}
|
||||
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
|
||||
{
|
||||
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
|
||||
static std::mutex mutex;
|
||||
|
||||
return {Success, std::move(emotes)};
|
||||
}
|
||||
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
|
||||
}
|
||||
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
|
||||
{
|
||||
auto emotes = EmoteMap();
|
||||
auto jsonEmotes = jsonRoot.value("emotes").toArray();
|
||||
auto urlTemplate = "https:" + jsonRoot.value("urlTemplate").toString();
|
||||
|
||||
for (auto jsonEmote_ : jsonEmotes) {
|
||||
auto jsonEmote = jsonEmote_.toObject();
|
||||
|
||||
auto id = EmoteId{jsonEmote.value("id").toString()};
|
||||
auto name = EmoteName{jsonEmote.value("code").toString()};
|
||||
// emoteObject.value("imageType").toString();
|
||||
|
||||
auto emote = Emote(
|
||||
{name,
|
||||
ImageSet{
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
|
||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
|
||||
Tooltip{name.string + "<br />Channel Bttv Emote"},
|
||||
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
||||
|
||||
emotes[name] = cachedOrMake(std::move(emote), id);
|
||||
}
|
||||
|
||||
return {Success, std::move(emotes)};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
//
|
||||
|
||||
@@ -13,82 +13,81 @@
|
||||
#include <memory>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
namespace {
|
||||
void parseEmoji(const std::shared_ptr<EmojiData> &emojiData,
|
||||
const rapidjson::Value &unparsedEmoji,
|
||||
QString shortCode = QString())
|
||||
{
|
||||
static uint unicodeBytes[4];
|
||||
|
||||
void parseEmoji(const std::shared_ptr<EmojiData> &emojiData,
|
||||
const rapidjson::Value &unparsedEmoji,
|
||||
QString shortCode = QString())
|
||||
{
|
||||
static uint unicodeBytes[4];
|
||||
struct {
|
||||
bool apple;
|
||||
bool google;
|
||||
bool twitter;
|
||||
bool emojione;
|
||||
bool facebook;
|
||||
bool messenger;
|
||||
} capabilities;
|
||||
|
||||
struct {
|
||||
bool apple;
|
||||
bool google;
|
||||
bool twitter;
|
||||
bool emojione;
|
||||
bool facebook;
|
||||
bool messenger;
|
||||
} capabilities;
|
||||
|
||||
if (!shortCode.isEmpty()) {
|
||||
emojiData->shortCodes.push_back(shortCode);
|
||||
} else {
|
||||
const auto &shortCodes = unparsedEmoji["short_names"];
|
||||
for (const auto &shortCode : shortCodes.GetArray()) {
|
||||
emojiData->shortCodes.emplace_back(shortCode.GetString());
|
||||
if (!shortCode.isEmpty()) {
|
||||
emojiData->shortCodes.push_back(shortCode);
|
||||
} else {
|
||||
const auto &shortCodes = unparsedEmoji["short_names"];
|
||||
for (const auto &shortCode : shortCodes.GetArray()) {
|
||||
emojiData->shortCodes.emplace_back(shortCode.GetString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rj::getSafe(unparsedEmoji, "non_qualified", emojiData->nonQualifiedCode);
|
||||
rj::getSafe(unparsedEmoji, "unified", emojiData->unifiedCode);
|
||||
rj::getSafe(unparsedEmoji, "non_qualified",
|
||||
emojiData->nonQualifiedCode);
|
||||
rj::getSafe(unparsedEmoji, "unified", emojiData->unifiedCode);
|
||||
|
||||
rj::getSafe(unparsedEmoji, "has_img_apple", capabilities.apple);
|
||||
rj::getSafe(unparsedEmoji, "has_img_google", capabilities.google);
|
||||
rj::getSafe(unparsedEmoji, "has_img_twitter", capabilities.twitter);
|
||||
rj::getSafe(unparsedEmoji, "has_img_emojione", capabilities.emojione);
|
||||
rj::getSafe(unparsedEmoji, "has_img_facebook", capabilities.facebook);
|
||||
rj::getSafe(unparsedEmoji, "has_img_messenger", capabilities.messenger);
|
||||
rj::getSafe(unparsedEmoji, "has_img_apple", capabilities.apple);
|
||||
rj::getSafe(unparsedEmoji, "has_img_google", capabilities.google);
|
||||
rj::getSafe(unparsedEmoji, "has_img_twitter", capabilities.twitter);
|
||||
rj::getSafe(unparsedEmoji, "has_img_emojione", capabilities.emojione);
|
||||
rj::getSafe(unparsedEmoji, "has_img_facebook", capabilities.facebook);
|
||||
rj::getSafe(unparsedEmoji, "has_img_messenger", capabilities.messenger);
|
||||
|
||||
if (capabilities.apple) {
|
||||
emojiData->capabilities.insert("Apple");
|
||||
}
|
||||
if (capabilities.google) {
|
||||
emojiData->capabilities.insert("Google");
|
||||
}
|
||||
if (capabilities.twitter) {
|
||||
emojiData->capabilities.insert("Twitter");
|
||||
}
|
||||
if (capabilities.emojione) {
|
||||
emojiData->capabilities.insert("EmojiOne 3");
|
||||
}
|
||||
if (capabilities.facebook) {
|
||||
emojiData->capabilities.insert("Facebook");
|
||||
}
|
||||
if (capabilities.messenger) {
|
||||
emojiData->capabilities.insert("Messenger");
|
||||
}
|
||||
if (capabilities.apple) {
|
||||
emojiData->capabilities.insert("Apple");
|
||||
}
|
||||
if (capabilities.google) {
|
||||
emojiData->capabilities.insert("Google");
|
||||
}
|
||||
if (capabilities.twitter) {
|
||||
emojiData->capabilities.insert("Twitter");
|
||||
}
|
||||
if (capabilities.emojione) {
|
||||
emojiData->capabilities.insert("EmojiOne 3");
|
||||
}
|
||||
if (capabilities.facebook) {
|
||||
emojiData->capabilities.insert("Facebook");
|
||||
}
|
||||
if (capabilities.messenger) {
|
||||
emojiData->capabilities.insert("Messenger");
|
||||
}
|
||||
|
||||
QStringList unicodeCharacters;
|
||||
if (!emojiData->nonQualifiedCode.isEmpty()) {
|
||||
unicodeCharacters = emojiData->nonQualifiedCode.toLower().split('-');
|
||||
} else {
|
||||
unicodeCharacters = emojiData->unifiedCode.toLower().split('-');
|
||||
QStringList unicodeCharacters;
|
||||
if (!emojiData->nonQualifiedCode.isEmpty()) {
|
||||
unicodeCharacters =
|
||||
emojiData->nonQualifiedCode.toLower().split('-');
|
||||
} else {
|
||||
unicodeCharacters = emojiData->unifiedCode.toLower().split('-');
|
||||
}
|
||||
if (unicodeCharacters.length() < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int numUnicodeBytes = 0;
|
||||
|
||||
for (const QString &unicodeCharacter : unicodeCharacters) {
|
||||
unicodeBytes[numUnicodeBytes++] =
|
||||
QString(unicodeCharacter).toUInt(nullptr, 16);
|
||||
}
|
||||
|
||||
emojiData->value = QString::fromUcs4(unicodeBytes, numUnicodeBytes);
|
||||
}
|
||||
if (unicodeCharacters.length() < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int numUnicodeBytes = 0;
|
||||
|
||||
for (const QString &unicodeCharacter : unicodeCharacters) {
|
||||
unicodeBytes[numUnicodeBytes++] =
|
||||
QString(unicodeCharacter).toUInt(nullptr, 16);
|
||||
}
|
||||
|
||||
emojiData->value = QString::fromUcs4(unicodeBytes, numUnicodeBytes);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Emojis::load()
|
||||
|
||||
@@ -10,99 +10,100 @@
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
|
||||
{
|
||||
auto emote = urls.value(emoteScale);
|
||||
if (emote.isUndefined()) {
|
||||
return {""};
|
||||
}
|
||||
|
||||
assert(emote.isString());
|
||||
|
||||
return {"https:" + emote.toString()};
|
||||
}
|
||||
void fillInEmoteData(const QJsonObject &urls, const EmoteName &name,
|
||||
const QString &tooltip, Emote &emoteData)
|
||||
{
|
||||
auto url1x = getEmoteLink(urls, "1");
|
||||
auto url2x = getEmoteLink(urls, "2");
|
||||
auto url3x = getEmoteLink(urls, "4");
|
||||
|
||||
//, code, tooltip
|
||||
emoteData.name = name;
|
||||
emoteData.images =
|
||||
ImageSet{Image::fromUrl(url1x, 1), Image::fromUrl(url2x, 0.5),
|
||||
Image::fromUrl(url3x, 0.25)};
|
||||
emoteData.tooltip = {tooltip};
|
||||
}
|
||||
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
|
||||
{
|
||||
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
|
||||
static std::mutex mutex;
|
||||
|
||||
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
|
||||
}
|
||||
std::pair<Outcome, EmoteMap> parseGlobalEmotes(const QJsonObject &jsonRoot,
|
||||
const EmoteMap ¤tEmotes)
|
||||
{
|
||||
auto jsonSets = jsonRoot.value("sets").toObject();
|
||||
auto emotes = EmoteMap();
|
||||
|
||||
for (auto jsonSet : jsonSets) {
|
||||
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
|
||||
|
||||
for (auto jsonEmoteValue : jsonEmotes) {
|
||||
auto jsonEmote = jsonEmoteValue.toObject();
|
||||
|
||||
auto name = EmoteName{jsonEmote.value("name").toString()};
|
||||
auto id = EmoteId{jsonEmote.value("id").toString()};
|
||||
auto urls = jsonEmote.value("urls").toObject();
|
||||
|
||||
auto emote = Emote();
|
||||
fillInEmoteData(urls, name, name.string + "<br/>Global FFZ Emote",
|
||||
emote);
|
||||
emote.homePage =
|
||||
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
|
||||
.arg(id.string)
|
||||
.arg(name.string)};
|
||||
|
||||
emotes[name] =
|
||||
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
|
||||
Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
|
||||
{
|
||||
auto emote = urls.value(emoteScale);
|
||||
if (emote.isUndefined()) {
|
||||
return {""};
|
||||
}
|
||||
|
||||
assert(emote.isString());
|
||||
|
||||
return {"https:" + emote.toString()};
|
||||
}
|
||||
void fillInEmoteData(const QJsonObject &urls, const EmoteName &name,
|
||||
const QString &tooltip, Emote &emoteData)
|
||||
{
|
||||
auto url1x = getEmoteLink(urls, "1");
|
||||
auto url2x = getEmoteLink(urls, "2");
|
||||
auto url3x = getEmoteLink(urls, "4");
|
||||
|
||||
return {Success, std::move(emotes)};
|
||||
}
|
||||
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
|
||||
{
|
||||
auto jsonSets = jsonRoot.value("sets").toObject();
|
||||
auto emotes = EmoteMap();
|
||||
//, code, tooltip
|
||||
emoteData.name = name;
|
||||
emoteData.images =
|
||||
ImageSet{Image::fromUrl(url1x, 1), Image::fromUrl(url2x, 0.5),
|
||||
Image::fromUrl(url3x, 0.25)};
|
||||
emoteData.tooltip = {tooltip};
|
||||
}
|
||||
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
|
||||
{
|
||||
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
|
||||
static std::mutex mutex;
|
||||
|
||||
for (auto jsonSet : jsonSets) {
|
||||
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
|
||||
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
|
||||
}
|
||||
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
|
||||
const QJsonObject &jsonRoot, const EmoteMap ¤tEmotes)
|
||||
{
|
||||
auto jsonSets = jsonRoot.value("sets").toObject();
|
||||
auto emotes = EmoteMap();
|
||||
|
||||
for (auto _jsonEmote : jsonEmotes) {
|
||||
auto jsonEmote = _jsonEmote.toObject();
|
||||
for (auto jsonSet : jsonSets) {
|
||||
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
|
||||
|
||||
// margins
|
||||
auto id = EmoteId{QString::number(jsonEmote.value("id").toInt())};
|
||||
auto name = EmoteName{jsonEmote.value("name").toString()};
|
||||
auto urls = jsonEmote.value("urls").toObject();
|
||||
for (auto jsonEmoteValue : jsonEmotes) {
|
||||
auto jsonEmote = jsonEmoteValue.toObject();
|
||||
|
||||
Emote emote;
|
||||
fillInEmoteData(urls, name, name.string + "<br/>Channel FFZ Emote",
|
||||
emote);
|
||||
emote.homePage =
|
||||
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
|
||||
.arg(id.string)
|
||||
.arg(name.string)};
|
||||
auto name = EmoteName{jsonEmote.value("name").toString()};
|
||||
auto id = EmoteId{jsonEmote.value("id").toString()};
|
||||
auto urls = jsonEmote.value("urls").toObject();
|
||||
|
||||
emotes[name] = cachedOrMake(std::move(emote), id);
|
||||
auto emote = Emote();
|
||||
fillInEmoteData(urls, name,
|
||||
name.string + "<br/>Global FFZ Emote", emote);
|
||||
emote.homePage =
|
||||
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
|
||||
.arg(id.string)
|
||||
.arg(name.string)};
|
||||
|
||||
emotes[name] =
|
||||
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {Success, std::move(emotes)};
|
||||
}
|
||||
return {Success, std::move(emotes)};
|
||||
}
|
||||
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
|
||||
{
|
||||
auto jsonSets = jsonRoot.value("sets").toObject();
|
||||
auto emotes = EmoteMap();
|
||||
|
||||
for (auto jsonSet : jsonSets) {
|
||||
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
|
||||
|
||||
for (auto _jsonEmote : jsonEmotes) {
|
||||
auto jsonEmote = _jsonEmote.toObject();
|
||||
|
||||
// margins
|
||||
auto id =
|
||||
EmoteId{QString::number(jsonEmote.value("id").toInt())};
|
||||
auto name = EmoteName{jsonEmote.value("name").toString()};
|
||||
auto urls = jsonEmote.value("urls").toObject();
|
||||
|
||||
Emote emote;
|
||||
fillInEmoteData(urls, name,
|
||||
name.string + "<br/>Channel FFZ Emote", emote);
|
||||
emote.homePage =
|
||||
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
|
||||
.arg(id.string)
|
||||
.arg(name.string)};
|
||||
|
||||
emotes[name] = cachedOrMake(std::move(emote), id);
|
||||
}
|
||||
}
|
||||
|
||||
return {Success, std::move(emotes)};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
FfzEmotes::FfzEmotes()
|
||||
|
||||
@@ -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