mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Migrated cheermotes to Helix API (#2440)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
7c4c797dbc
commit
519855d852
9 changed files with 159 additions and 401 deletions
|
@ -15,6 +15,7 @@
|
||||||
- Bugfix: Fix Ctrl + Backspace not closing colon emote picker. (#2780)
|
- Bugfix: Fix Ctrl + Backspace not closing colon emote picker. (#2780)
|
||||||
- Bugfix: Approving/denying AutoMod messages works again. (#2779)
|
- Bugfix: Approving/denying AutoMod messages works again. (#2779)
|
||||||
- Dev: Migrated AutoMod approve/deny endpoints to Helix. (#2779)
|
- Dev: Migrated AutoMod approve/deny endpoints to Helix. (#2779)
|
||||||
|
- Dev: Migrated Get Cheermotes endpoint to Helix. (#2440)
|
||||||
|
|
||||||
## 2.3.1
|
## 2.3.1
|
||||||
|
|
||||||
|
|
|
@ -220,7 +220,6 @@ SOURCES += \
|
||||||
src/providers/twitch/TwitchHelpers.cpp \
|
src/providers/twitch/TwitchHelpers.cpp \
|
||||||
src/providers/twitch/TwitchIrcServer.cpp \
|
src/providers/twitch/TwitchIrcServer.cpp \
|
||||||
src/providers/twitch/TwitchMessageBuilder.cpp \
|
src/providers/twitch/TwitchMessageBuilder.cpp \
|
||||||
src/providers/twitch/TwitchParseCheerEmotes.cpp \
|
|
||||||
src/providers/twitch/TwitchUser.cpp \
|
src/providers/twitch/TwitchUser.cpp \
|
||||||
src/RunGui.cpp \
|
src/RunGui.cpp \
|
||||||
src/singletons/Badges.cpp \
|
src/singletons/Badges.cpp \
|
||||||
|
@ -459,7 +458,6 @@ HEADERS += \
|
||||||
src/providers/twitch/TwitchHelpers.hpp \
|
src/providers/twitch/TwitchHelpers.hpp \
|
||||||
src/providers/twitch/TwitchIrcServer.hpp \
|
src/providers/twitch/TwitchIrcServer.hpp \
|
||||||
src/providers/twitch/TwitchMessageBuilder.hpp \
|
src/providers/twitch/TwitchMessageBuilder.hpp \
|
||||||
src/providers/twitch/TwitchParseCheerEmotes.hpp \
|
|
||||||
src/providers/twitch/TwitchUser.hpp \
|
src/providers/twitch/TwitchUser.hpp \
|
||||||
src/RunGui.hpp \
|
src/RunGui.hpp \
|
||||||
src/singletons/Badges.hpp \
|
src/singletons/Badges.hpp \
|
||||||
|
|
|
@ -217,8 +217,6 @@ set(SOURCE_FILES main.cpp
|
||||||
providers/twitch/TwitchIrcServer.hpp
|
providers/twitch/TwitchIrcServer.hpp
|
||||||
providers/twitch/TwitchMessageBuilder.cpp
|
providers/twitch/TwitchMessageBuilder.cpp
|
||||||
providers/twitch/TwitchMessageBuilder.hpp
|
providers/twitch/TwitchMessageBuilder.hpp
|
||||||
providers/twitch/TwitchParseCheerEmotes.cpp
|
|
||||||
providers/twitch/TwitchParseCheerEmotes.hpp
|
|
||||||
providers/twitch/TwitchUser.cpp
|
providers/twitch/TwitchUser.cpp
|
||||||
providers/twitch/TwitchUser.hpp
|
providers/twitch/TwitchUser.hpp
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
#include "providers/twitch/PubsubClient.hpp"
|
#include "providers/twitch/PubsubClient.hpp"
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||||
#include "providers/twitch/TwitchParseCheerEmotes.hpp"
|
|
||||||
#include "providers/twitch/api/Helix.hpp"
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
#include "providers/twitch/api/Kraken.hpp"
|
#include "providers/twitch/api/Kraken.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
|
@ -910,28 +909,26 @@ void TwitchChannel::refreshBadges()
|
||||||
|
|
||||||
void TwitchChannel::refreshCheerEmotes()
|
void TwitchChannel::refreshCheerEmotes()
|
||||||
{
|
{
|
||||||
QString url("https://api.twitch.tv/kraken/bits/actions?channel_id=" +
|
getHelix()->getCheermotes(
|
||||||
this->roomId());
|
this->roomId(),
|
||||||
NetworkRequest::twitchRequest(url)
|
[this, weak = weakOf<Channel>(this)](
|
||||||
.onSuccess([this,
|
const std::vector<HelixCheermoteSet> &cheermoteSets) -> Outcome {
|
||||||
weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
|
||||||
auto shared = weak.lock();
|
auto shared = weak.lock();
|
||||||
if (!shared)
|
if (!shared)
|
||||||
{
|
{
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson());
|
|
||||||
std::vector<CheerEmoteSet> emoteSets;
|
std::vector<CheerEmoteSet> emoteSets;
|
||||||
|
|
||||||
for (auto &set : cheerEmoteSets)
|
for (const auto &set : cheermoteSets)
|
||||||
{
|
{
|
||||||
auto cheerEmoteSet = CheerEmoteSet();
|
auto cheerEmoteSet = CheerEmoteSet();
|
||||||
cheerEmoteSet.regex = QRegularExpression(
|
cheerEmoteSet.regex = QRegularExpression(
|
||||||
"^" + set.prefix + "([1-9][0-9]*)$",
|
"^" + set.prefix + "([1-9][0-9]*)$",
|
||||||
QRegularExpression::CaseInsensitiveOption);
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
|
|
||||||
for (auto &tier : set.tiers)
|
for (const auto &tier : set.tiers)
|
||||||
{
|
{
|
||||||
CheerEmote cheerEmote;
|
CheerEmote cheerEmote;
|
||||||
|
|
||||||
|
@ -949,36 +946,42 @@ void TwitchChannel::refreshCheerEmotes()
|
||||||
cheerEmote.animatedEmote = std::make_shared<Emote>(
|
cheerEmote.animatedEmote = std::make_shared<Emote>(
|
||||||
Emote{EmoteName{"cheer emote"},
|
Emote{EmoteName{"cheer emote"},
|
||||||
ImageSet{
|
ImageSet{
|
||||||
tier.images["dark"]["animated"]["1"],
|
tier.darkAnimated.imageURL1x,
|
||||||
tier.images["dark"]["animated"]["2"],
|
tier.darkAnimated.imageURL2x,
|
||||||
tier.images["dark"]["animated"]["4"],
|
tier.darkAnimated.imageURL4x,
|
||||||
},
|
},
|
||||||
Tooltip{emoteTooltip}, Url{}});
|
Tooltip{emoteTooltip}, Url{}});
|
||||||
cheerEmote.staticEmote = std::make_shared<Emote>(
|
cheerEmote.staticEmote = std::make_shared<Emote>(
|
||||||
Emote{EmoteName{"cheer emote"},
|
Emote{EmoteName{"cheer emote"},
|
||||||
ImageSet{
|
ImageSet{
|
||||||
tier.images["dark"]["static"]["1"],
|
tier.darkStatic.imageURL1x,
|
||||||
tier.images["dark"]["static"]["2"],
|
tier.darkStatic.imageURL2x,
|
||||||
tier.images["dark"]["static"]["4"],
|
tier.darkStatic.imageURL4x,
|
||||||
},
|
},
|
||||||
Tooltip{emoteTooltip}, Url{}});
|
Tooltip{emoteTooltip}, Url{}});
|
||||||
|
|
||||||
cheerEmoteSet.cheerEmotes.emplace_back(cheerEmote);
|
cheerEmoteSet.cheerEmotes.emplace_back(
|
||||||
|
std::move(cheerEmote));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort cheermotes by cost
|
||||||
std::sort(cheerEmoteSet.cheerEmotes.begin(),
|
std::sort(cheerEmoteSet.cheerEmotes.begin(),
|
||||||
cheerEmoteSet.cheerEmotes.end(),
|
cheerEmoteSet.cheerEmotes.end(),
|
||||||
[](const auto &lhs, const auto &rhs) {
|
[](const auto &lhs, const auto &rhs) {
|
||||||
return lhs.minBits > rhs.minBits;
|
return lhs.minBits > rhs.minBits;
|
||||||
});
|
});
|
||||||
|
|
||||||
emoteSets.emplace_back(cheerEmoteSet);
|
emoteSets.emplace_back(std::move(cheerEmoteSet));
|
||||||
}
|
}
|
||||||
|
|
||||||
*this->cheerEmoteSets_.access() = std::move(emoteSets);
|
*this->cheerEmoteSets_.access() = std::move(emoteSets);
|
||||||
|
|
||||||
return Success;
|
return Success;
|
||||||
})
|
},
|
||||||
.execute();
|
[] {
|
||||||
|
// Failure
|
||||||
|
return Failure;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchChannel::createClip()
|
void TwitchChannel::createClip()
|
||||||
|
|
|
@ -1,321 +0,0 @@
|
||||||
#include "TwitchParseCheerEmotes.hpp"
|
|
||||||
|
|
||||||
#include <rapidjson/document.h>
|
|
||||||
#include <QString>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
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())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = Image::fromUrl({url}, chatterinoScale);
|
|
||||||
|
|
||||||
// TODO(pajlada): Fill in name and tooltip
|
|
||||||
tier.images[background][state][scale] = image;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set.tiers.emplace_back(tier);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// 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
|
|
||||||
std::vector<JSONCheermoteSet> ParseCheermoteSets(const rapidjson::Document &d)
|
|
||||||
{
|
|
||||||
std::vector<JSONCheermoteSet> sets;
|
|
||||||
|
|
||||||
if (!d.IsObject())
|
|
||||||
{
|
|
||||||
return sets;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!d.HasMember("actions"))
|
|
||||||
{
|
|
||||||
return sets;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto &actionsValue = d["actions"];
|
|
||||||
|
|
||||||
if (!actionsValue.IsArray())
|
|
||||||
{
|
|
||||||
return sets;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &action : actionsValue.GetArray())
|
|
||||||
{
|
|
||||||
JSONCheermoteSet set;
|
|
||||||
bool res = ParseSingleCheermoteSet(set, action);
|
|
||||||
|
|
||||||
if (res)
|
|
||||||
{
|
|
||||||
sets.emplace_back(set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sets;
|
|
||||||
}
|
|
||||||
} // namespace chatterino
|
|
|
@ -1,37 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <rapidjson/document.h>
|
|
||||||
#include <QString>
|
|
||||||
#include <map>
|
|
||||||
#include <vector>
|
|
||||||
#include "messages/Image.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
struct JSONCheermoteSet {
|
|
||||||
QString prefix;
|
|
||||||
std::vector<QString> scales;
|
|
||||||
|
|
||||||
std::vector<QString> backgrounds;
|
|
||||||
std::vector<QString> states;
|
|
||||||
|
|
||||||
QString type;
|
|
||||||
QString updatedAt;
|
|
||||||
int priority;
|
|
||||||
|
|
||||||
struct CheermoteTier {
|
|
||||||
int minBits;
|
|
||||||
QString id;
|
|
||||||
QString color;
|
|
||||||
|
|
||||||
// Background State Scale
|
|
||||||
std::map<QString, std::map<QString, std::map<QString, ImagePtr>>>
|
|
||||||
images;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<CheermoteTier> tiers;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<JSONCheermoteSet> ParseCheermoteSets(const rapidjson::Document &d);
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
|
@ -722,6 +722,45 @@ void Helix::manageAutoModMessages(
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Helix::getCheermotes(
|
||||||
|
QString broadcasterId,
|
||||||
|
ResultCallback<std::vector<HelixCheermoteSet>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback)
|
||||||
|
{
|
||||||
|
QUrlQuery urlQuery;
|
||||||
|
|
||||||
|
urlQuery.addQueryItem("broadcaster_id", broadcasterId);
|
||||||
|
|
||||||
|
this->makeRequest("bits/cheermotes", urlQuery)
|
||||||
|
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||||
|
auto root = result.parseJson();
|
||||||
|
auto data = root.value("data");
|
||||||
|
|
||||||
|
if (!data.isArray())
|
||||||
|
{
|
||||||
|
failureCallback();
|
||||||
|
return Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<HelixCheermoteSet> cheermoteSets;
|
||||||
|
|
||||||
|
for (const auto &jsonStream : data.toArray())
|
||||||
|
{
|
||||||
|
cheermoteSets.emplace_back(jsonStream.toObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
successCallback(cheermoteSets);
|
||||||
|
return Success;
|
||||||
|
})
|
||||||
|
.onError([broadcasterId, failureCallback](NetworkResult result) {
|
||||||
|
qCDebug(chatterinoTwitch)
|
||||||
|
<< "Failed to get cheermotes(broadcaster_id=" << broadcasterId
|
||||||
|
<< "): " << result.status() << result.getData();
|
||||||
|
failureCallback();
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
NetworkRequest Helix::makeRequest(QString url, QUrlQuery urlQuery)
|
NetworkRequest Helix::makeRequest(QString url, QUrlQuery urlQuery)
|
||||||
{
|
{
|
||||||
assert(!url.startsWith("/"));
|
assert(!url.startsWith("/"));
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/Aliases.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
|
@ -193,6 +194,76 @@ struct HelixBlock {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct HelixCheermoteImage {
|
||||||
|
Url imageURL1x;
|
||||||
|
Url imageURL2x;
|
||||||
|
Url imageURL4x;
|
||||||
|
|
||||||
|
explicit HelixCheermoteImage(QJsonObject jsonObject)
|
||||||
|
: imageURL1x(Url{jsonObject.value("1").toString()})
|
||||||
|
, imageURL2x(Url{jsonObject.value("2").toString()})
|
||||||
|
, imageURL4x(Url{jsonObject.value("4").toString()})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HelixCheermoteTier {
|
||||||
|
QString id;
|
||||||
|
QString color;
|
||||||
|
int minBits;
|
||||||
|
HelixCheermoteImage darkAnimated;
|
||||||
|
HelixCheermoteImage darkStatic;
|
||||||
|
HelixCheermoteImage lightAnimated;
|
||||||
|
HelixCheermoteImage lightStatic;
|
||||||
|
|
||||||
|
explicit HelixCheermoteTier(QJsonObject jsonObject)
|
||||||
|
: id(jsonObject.value("id").toString())
|
||||||
|
, color(jsonObject.value("color").toString())
|
||||||
|
, minBits(jsonObject.value("min_bits").toInt())
|
||||||
|
, darkAnimated(jsonObject.value("images")
|
||||||
|
.toObject()
|
||||||
|
.value("dark")
|
||||||
|
.toObject()
|
||||||
|
.value("animated")
|
||||||
|
.toObject())
|
||||||
|
, darkStatic(jsonObject.value("images")
|
||||||
|
.toObject()
|
||||||
|
.value("dark")
|
||||||
|
.toObject()
|
||||||
|
.value("static")
|
||||||
|
.toObject())
|
||||||
|
, lightAnimated(jsonObject.value("images")
|
||||||
|
.toObject()
|
||||||
|
.value("light")
|
||||||
|
.toObject()
|
||||||
|
.value("animated")
|
||||||
|
.toObject())
|
||||||
|
, lightStatic(jsonObject.value("images")
|
||||||
|
.toObject()
|
||||||
|
.value("light")
|
||||||
|
.toObject()
|
||||||
|
.value("static")
|
||||||
|
.toObject())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HelixCheermoteSet {
|
||||||
|
QString prefix;
|
||||||
|
QString type;
|
||||||
|
std::vector<HelixCheermoteTier> tiers;
|
||||||
|
|
||||||
|
explicit HelixCheermoteSet(QJsonObject jsonObject)
|
||||||
|
: prefix(jsonObject.value("prefix").toString())
|
||||||
|
, type(jsonObject.value("type").toString())
|
||||||
|
{
|
||||||
|
for (const auto &tier : jsonObject.value("tiers").toArray())
|
||||||
|
{
|
||||||
|
this->tiers.emplace_back(tier.toObject());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
enum class HelixClipError {
|
enum class HelixClipError {
|
||||||
Unknown,
|
Unknown,
|
||||||
ClipsDisabled,
|
ClipsDisabled,
|
||||||
|
@ -321,6 +392,12 @@ public:
|
||||||
std::function<void()> successCallback,
|
std::function<void()> successCallback,
|
||||||
std::function<void(HelixAutoModMessageError)> failureCallback);
|
std::function<void(HelixAutoModMessageError)> failureCallback);
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference/#get-cheermotes
|
||||||
|
void getCheermotes(
|
||||||
|
QString broadcasterId,
|
||||||
|
ResultCallback<std::vector<HelixCheermoteSet>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback);
|
||||||
|
|
||||||
void update(QString clientId, QString oauthToken);
|
void update(QString clientId, QString oauthToken);
|
||||||
|
|
||||||
static void initialize();
|
static void initialize();
|
||||||
|
|
|
@ -6,14 +6,6 @@ this folder describes what sort of API requests we do, what permissions are requ
|
||||||
|
|
||||||
We use few Kraken endpoints in Chatterino2.
|
We use few Kraken endpoints in Chatterino2.
|
||||||
|
|
||||||
### Get Cheermotes
|
|
||||||
|
|
||||||
URL: https://dev.twitch.tv/docs/v5/reference/bits#get-cheermotes
|
|
||||||
|
|
||||||
Migration path: **Not checked**
|
|
||||||
|
|
||||||
- We implement this API in `providers/twitch/TwitchChannel.cpp` to resolve a chats available cheer emotes. This helps us parse incoming messages like `pajaCheer1000`
|
|
||||||
|
|
||||||
### Get User Emotes
|
### Get User Emotes
|
||||||
|
|
||||||
URL: https://dev.twitch.tv/docs/v5/reference/users#get-user-emotes
|
URL: https://dev.twitch.tv/docs/v5/reference/users#get-user-emotes
|
||||||
|
@ -157,6 +149,14 @@ Requires `moderator:manage:automod` scope
|
||||||
Used in:
|
Used in:
|
||||||
- `providers/twitch/TwitchAccount.cpp` to approve/deny held AutoMod messages
|
- `providers/twitch/TwitchAccount.cpp` to approve/deny held AutoMod messages
|
||||||
|
|
||||||
|
### Get Cheermotes
|
||||||
|
|
||||||
|
URL: https://dev.twitch.tv/docs/api/reference/#get-cheermotes
|
||||||
|
|
||||||
|
- We implement this in `providers/twitch/api/Helix.cpp getCheermotes`
|
||||||
|
Used in:
|
||||||
|
- `providers/twitch/TwitchChannel.cpp` to resolve a chats available cheer emotes. This helps us parse incoming messages like `pajaCheer1000`
|
||||||
|
|
||||||
## TMI
|
## TMI
|
||||||
|
|
||||||
The TMI api is undocumented.
|
The TMI api is undocumented.
|
||||||
|
|
Loading…
Reference in a new issue