Initial custom and channel-specific cheermote parsing done
Needs more testing once the rendering pipeline is complete again Fixes #74
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
#include "resourcemanager.hpp"
|
||||
//#include "singletons/emotemanager.hpp"
|
||||
//#include "singletons/windowmanager.hpp"
|
||||
#include "util/urlfetch.hpp"
|
||||
|
||||
#include <QPixmap>
|
||||
@@ -15,6 +13,257 @@ inline messages::Image *lli(const char *pixmapPath, qreal scale = 1)
|
||||
return new messages::Image(new QPixmap(pixmapPath), scale);
|
||||
}
|
||||
|
||||
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()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out.emplace_back(innerValue.GetString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse a single cheermote set (or "action") from the twitch api
|
||||
inline bool ParseSingleCheermoteSet(ResourceManager::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()) {
|
||||
ResourceManager::JSONCheermoteSet::CheermoteTier tier;
|
||||
|
||||
if (!tierValue.IsObject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadValue(tierValue, "min_bits", tier.minBits)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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 (!backgroundExists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const rapidjson::Value &imageBackgroundStates = imageBackgroundValue.value;
|
||||
if (!imageBackgroundStates.IsObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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 (!stateExists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const rapidjson::Value &imageScalesValue = imageBackgroundState.value;
|
||||
if (!imageScalesValue.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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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 = new messages::Image(url, chatterinoScale);
|
||||
|
||||
// TODO(pajlada): Fill in name and tooltip
|
||||
tier.images[background][state][scale] = image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set.tiers.emplace_back(tier);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Look through the results of https://api.twitch.tv/kraken/bits/actions?channel_id=11148817 for
|
||||
// cheermote sets or "Actions" as they are called in the API
|
||||
inline void ParseCheermoteSets(std::vector<ResourceManager::JSONCheermoteSet> &sets,
|
||||
const rapidjson::Document &d)
|
||||
{
|
||||
if (!d.IsObject()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!d.HasMember("actions")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &actionsValue = d["actions"];
|
||||
|
||||
if (!actionsValue.IsArray()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &action : actionsValue.GetArray()) {
|
||||
ResourceManager::JSONCheermoteSet set;
|
||||
bool res = ParseSingleCheermoteSet(set, action);
|
||||
|
||||
if (res) {
|
||||
sets.emplace_back(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ResourceManager::ResourceManager()
|
||||
@@ -90,6 +339,48 @@ void ResourceManager::loadChannelData(const QString &roomID, bool bypassCache)
|
||||
|
||||
ch.loaded = true;
|
||||
});
|
||||
|
||||
QString cheermoteURL = "https://api.twitch.tv/kraken/bits/actions?channel_id=" + roomID;
|
||||
|
||||
util::twitch::get2(
|
||||
cheermoteURL, QThread::currentThread(), [this, roomID](const rapidjson::Document &d) {
|
||||
ResourceManager::Channel &ch = this->channels[roomID];
|
||||
|
||||
ParseCheermoteSets(ch.jsonCheermoteSets, d);
|
||||
|
||||
for (auto &set : ch.jsonCheermoteSets) {
|
||||
CheermoteSet cheermoteSet;
|
||||
cheermoteSet.regex =
|
||||
QRegularExpression("^" + set.prefix.toLower() + "([1-9][0-9]*)$");
|
||||
|
||||
for (auto &tier : set.tiers) {
|
||||
Cheermote cheermote;
|
||||
|
||||
cheermote.color = QColor(tier.color);
|
||||
cheermote.minBits = tier.minBits;
|
||||
|
||||
// TODO(pajlada): We currently hardcode dark here :|
|
||||
// We will continue to do so for now since we haven't had to
|
||||
// solve that anywhere else
|
||||
cheermote.emoteDataAnimated.image1x = tier.images["dark"]["animated"]["1"];
|
||||
cheermote.emoteDataAnimated.image2x = tier.images["dark"]["animated"]["2"];
|
||||
cheermote.emoteDataAnimated.image3x = tier.images["dark"]["animated"]["4"];
|
||||
|
||||
cheermote.emoteDataStatic.image1x = tier.images["dark"]["static"]["1"];
|
||||
cheermote.emoteDataStatic.image2x = tier.images["dark"]["static"]["2"];
|
||||
cheermote.emoteDataStatic.image3x = tier.images["dark"]["static"]["4"];
|
||||
|
||||
cheermoteSet.cheermotes.emplace_back(cheermote);
|
||||
}
|
||||
|
||||
std::sort(cheermoteSet.cheermotes.begin(), cheermoteSet.cheermotes.end(),
|
||||
[](const auto &lhs, const auto &rhs) {
|
||||
return lhs.minBits < rhs.minBits; //
|
||||
});
|
||||
|
||||
ch.cheermoteSets.emplace_back(cheermoteSet);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ResourceManager::loadDynamicTwitchBadges()
|
||||
@@ -153,5 +444,5 @@ void ResourceManager::loadChatterinoBadges()
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace singletons
|
||||
} // namespace chatterino
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user