Twitch API: v5 to Helix migration (#1560)
There's a document in src/providers/twitch/api which describes how we interact with the Twitch API. Keeping this up to date might be a healthy way for us to ensure we keep using the right APIs for the right job.
This commit is contained in:
@@ -1,85 +0,0 @@
|
||||
#include "providers/twitch/PartialTwitchUser.hpp"
|
||||
|
||||
#include "common/Common.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <cassert>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
PartialTwitchUser PartialTwitchUser::byName(const QString &username)
|
||||
{
|
||||
PartialTwitchUser user;
|
||||
user.username_ = username;
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
PartialTwitchUser PartialTwitchUser::byId(const QString &id)
|
||||
{
|
||||
PartialTwitchUser user;
|
||||
user.id_ = id;
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
|
||||
const QObject *caller)
|
||||
{
|
||||
getId(
|
||||
successCallback, [] {}, caller);
|
||||
}
|
||||
void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
|
||||
std::function<void()> failureCallback,
|
||||
const QObject *caller)
|
||||
{
|
||||
assert(!this->username_.isEmpty());
|
||||
|
||||
NetworkRequest("https://api.twitch.tv/kraken/users?login=" +
|
||||
this->username_)
|
||||
.caller(caller)
|
||||
.authorizeTwitchV5(getDefaultClientID())
|
||||
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||
auto root = result.parseJson();
|
||||
if (!root.value("users").isArray())
|
||||
{
|
||||
qDebug()
|
||||
<< "API Error while getting user id, users is not an array";
|
||||
failureCallback();
|
||||
return Failure;
|
||||
}
|
||||
|
||||
auto users = root.value("users").toArray();
|
||||
if (users.size() != 1)
|
||||
{
|
||||
qDebug() << "API Error while getting user id, users array size "
|
||||
"is not 1";
|
||||
failureCallback();
|
||||
return Failure;
|
||||
}
|
||||
if (!users[0].isObject())
|
||||
{
|
||||
qDebug() << "API Error while getting user id, first user is "
|
||||
"not an object";
|
||||
failureCallback();
|
||||
return Failure;
|
||||
}
|
||||
auto firstUser = users[0].toObject();
|
||||
auto id = firstUser.value("_id");
|
||||
if (!id.isString())
|
||||
{
|
||||
qDebug() << "API Error: while getting user id, first user "
|
||||
"object `_id` key is not a string";
|
||||
failureCallback();
|
||||
return Failure;
|
||||
}
|
||||
successCallback(id.toString());
|
||||
|
||||
return Success;
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,30 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// Experimental class to test a method of calling APIs on twitch users
|
||||
class PartialTwitchUser
|
||||
{
|
||||
PartialTwitchUser() = default;
|
||||
|
||||
QString username_;
|
||||
QString id_;
|
||||
|
||||
public:
|
||||
static PartialTwitchUser byName(const QString &username);
|
||||
static PartialTwitchUser byId(const QString &id);
|
||||
|
||||
void getId(std::function<void(QString)> successCallback,
|
||||
const QObject *caller = nullptr);
|
||||
|
||||
void getId(std::function<void(QString)> successCallback,
|
||||
std::function<void()> failureCallback,
|
||||
const QObject *caller = nullptr);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -6,8 +6,8 @@
|
||||
#include "common/Env.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "providers/twitch/PartialTwitchUser.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
@@ -151,12 +151,14 @@ void TwitchAccount::ignore(
|
||||
const QString &targetName,
|
||||
std::function<void(IgnoreResult, const QString &)> onFinished)
|
||||
{
|
||||
const auto onIdFetched = [this, targetName,
|
||||
onFinished](QString targetUserId) {
|
||||
this->ignoreByID(targetUserId, targetName, onFinished); //
|
||||
const auto onUserFetched = [this, targetName,
|
||||
onFinished](const auto &user) {
|
||||
this->ignoreByID(user.id, targetName, onFinished); //
|
||||
};
|
||||
|
||||
PartialTwitchUser::byName(targetName).getId(onIdFetched);
|
||||
const auto onUserFetchFailed = [] {};
|
||||
|
||||
getHelix()->getUserByName(targetName, onUserFetched, onUserFetchFailed);
|
||||
}
|
||||
|
||||
void TwitchAccount::ignoreByID(
|
||||
@@ -226,12 +228,14 @@ void TwitchAccount::unignore(
|
||||
const QString &targetName,
|
||||
std::function<void(UnignoreResult, const QString &message)> onFinished)
|
||||
{
|
||||
const auto onIdFetched = [this, targetName,
|
||||
onFinished](QString targetUserId) {
|
||||
this->unignoreByID(targetUserId, targetName, onFinished); //
|
||||
const auto onUserFetched = [this, targetName,
|
||||
onFinished](const auto &user) {
|
||||
this->unignoreByID(user.id, targetName, onFinished); //
|
||||
};
|
||||
|
||||
PartialTwitchUser::byName(targetName).getId(onIdFetched);
|
||||
const auto onUserFetchFailed = [] {};
|
||||
|
||||
getHelix()->getUserByName(targetName, onUserFetched, onUserFetchFailed);
|
||||
}
|
||||
|
||||
void TwitchAccount::unignoreByID(
|
||||
@@ -270,28 +274,18 @@ void TwitchAccount::unignoreByID(
|
||||
void TwitchAccount::checkFollow(const QString targetUserID,
|
||||
std::function<void(FollowResult)> onFinished)
|
||||
{
|
||||
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() +
|
||||
"/follows/channels/" + targetUserID);
|
||||
const auto onResponse = [onFinished](bool following, const auto &record) {
|
||||
if (!following)
|
||||
{
|
||||
onFinished(FollowResult_NotFollowing);
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkRequest(url)
|
||||
onFinished(FollowResult_Following);
|
||||
};
|
||||
|
||||
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
|
||||
.onError([=](NetworkResult result) {
|
||||
if (result.status() == 203)
|
||||
{
|
||||
onFinished(FollowResult_NotFollowing);
|
||||
}
|
||||
else
|
||||
{
|
||||
onFinished(FollowResult_Failed);
|
||||
}
|
||||
})
|
||||
.onSuccess([=](auto result) -> Outcome {
|
||||
auto document = result.parseRapidJson();
|
||||
onFinished(FollowResult_Following);
|
||||
return Success;
|
||||
})
|
||||
.execute();
|
||||
getHelix()->getUserFollow(this->getUserId(), targetUserID, onResponse,
|
||||
[] {});
|
||||
}
|
||||
|
||||
void TwitchAccount::followUser(const QString userID,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "common/Common.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/api/Kraken.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
@@ -141,6 +143,8 @@ void TwitchAccountManager::load()
|
||||
if (user)
|
||||
{
|
||||
qDebug() << "Twitch user updated to" << newUsername;
|
||||
getHelix()->update(user->getOAuthClient(), user->getOAuthToken());
|
||||
getKraken()->update(user->getOAuthClient(), user->getOAuthToken());
|
||||
this->currentUser_ = user;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
#include "providers/twitch/TwitchApi.hpp"
|
||||
|
||||
#include "common/Common.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
void TwitchApi::findUserId(const QString user,
|
||||
std::function<void(QString)> successCallback)
|
||||
{
|
||||
QString requestUrl("https://api.twitch.tv/kraken/users?login=" + user);
|
||||
|
||||
NetworkRequest(requestUrl)
|
||||
|
||||
.authorizeTwitchV5(getDefaultClientID())
|
||||
.timeout(30000)
|
||||
.onSuccess([successCallback](auto result) mutable -> Outcome {
|
||||
auto root = result.parseJson();
|
||||
if (!root.value("users").isArray())
|
||||
{
|
||||
qDebug()
|
||||
<< "API Error while getting user id, users is not an array";
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
auto users = root.value("users").toArray();
|
||||
if (users.size() != 1)
|
||||
{
|
||||
qDebug() << "API Error while getting user id, users array size "
|
||||
"is not 1";
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
if (!users[0].isObject())
|
||||
{
|
||||
qDebug() << "API Error while getting user id, first user is "
|
||||
"not an object";
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
auto firstUser = users[0].toObject();
|
||||
auto id = firstUser.value("_id");
|
||||
if (!id.isString())
|
||||
{
|
||||
qDebug() << "API Error: while getting user id, first user "
|
||||
"object `_id` key is not a string";
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
successCallback(id.toString());
|
||||
return Success;
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
void TwitchApi::findUserName(const QString userid,
|
||||
std::function<void(QString)> successCallback)
|
||||
{
|
||||
QString requestUrl("https://api.twitch.tv/kraken/users/" + userid);
|
||||
|
||||
NetworkRequest(requestUrl)
|
||||
|
||||
.authorizeTwitchV5(getDefaultClientID())
|
||||
.timeout(30000)
|
||||
.onSuccess([successCallback](auto result) mutable -> Outcome {
|
||||
auto root = result.parseJson();
|
||||
auto name = root.value("name");
|
||||
if (!name.isString())
|
||||
{
|
||||
qDebug() << "API Error: while getting user name, `name` is not "
|
||||
"a string";
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
successCallback(name.toString());
|
||||
return Success;
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class TwitchApi
|
||||
{
|
||||
public:
|
||||
static void findUserId(const QString user,
|
||||
std::function<void(QString)> callback);
|
||||
static void findUserName(const QString userid,
|
||||
std::function<void(QString)> callback);
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -14,6 +14,8 @@
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
#include "providers/twitch/TwitchParseCheerEmotes.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/api/Kraken.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Toasts.hpp"
|
||||
@@ -21,6 +23,7 @@
|
||||
#include "util/PostToThread.hpp"
|
||||
#include "widgets/Window.hpp"
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <IrcConnection>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
@@ -454,35 +457,25 @@ void TwitchChannel::refreshTitle()
|
||||
}
|
||||
this->titleRefreshedTime_ = QTime::currentTime();
|
||||
|
||||
QString url("https://api.twitch.tv/kraken/channels/" + roomID);
|
||||
NetworkRequest::twitchRequest(url)
|
||||
.onSuccess(
|
||||
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||
ChannelPtr shared = weak.lock();
|
||||
if (!shared)
|
||||
return Failure;
|
||||
const auto onSuccess = [this,
|
||||
weak = weakOf<Channel>(this)](const auto &channel) {
|
||||
ChannelPtr shared = weak.lock();
|
||||
if (!shared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto document = result.parseRapidJson();
|
||||
{
|
||||
auto status = this->streamStatus_.access();
|
||||
status->title = channel.status;
|
||||
}
|
||||
|
||||
auto statusIt = document.FindMember("status");
|
||||
this->liveStatusChanged.invoke();
|
||||
};
|
||||
|
||||
if (statusIt == document.MemberEnd())
|
||||
{
|
||||
return Failure;
|
||||
}
|
||||
const auto onFailure = [] {};
|
||||
|
||||
{
|
||||
auto status = this->streamStatus_.access();
|
||||
if (!rj::getSafe(statusIt->value, status->title))
|
||||
{
|
||||
return Failure;
|
||||
}
|
||||
}
|
||||
|
||||
this->liveStatusChanged.invoke();
|
||||
return Success;
|
||||
})
|
||||
.execute();
|
||||
getKraken()->getChannel(roomID, onSuccess, onFailure);
|
||||
}
|
||||
|
||||
void TwitchChannel::refreshLiveStatus()
|
||||
@@ -497,106 +490,73 @@ void TwitchChannel::refreshLiveStatus()
|
||||
return;
|
||||
}
|
||||
|
||||
QString url("https://api.twitch.tv/kraken/streams/" + roomID);
|
||||
getHelix()->getStreamById(
|
||||
roomID,
|
||||
[this, weak = weakOf<Channel>(this)](bool live, const auto &stream) {
|
||||
ChannelPtr shared = weak.lock();
|
||||
if (!shared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// auto request = makeGetStreamRequest(roomID, QThread::currentThread());
|
||||
NetworkRequest::twitchRequest(url)
|
||||
|
||||
.onSuccess(
|
||||
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||
ChannelPtr shared = weak.lock();
|
||||
if (!shared)
|
||||
return Failure;
|
||||
|
||||
return this->parseLiveStatus(result.parseRapidJson());
|
||||
})
|
||||
.execute();
|
||||
this->parseLiveStatus(live, stream);
|
||||
},
|
||||
[] {
|
||||
// failure
|
||||
});
|
||||
}
|
||||
|
||||
Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
||||
void TwitchChannel::parseLiveStatus(bool live, const HelixStream &stream)
|
||||
{
|
||||
if (!document.IsObject())
|
||||
if (!live)
|
||||
{
|
||||
qDebug() << "[TwitchChannel:refreshLiveStatus] root is not an object";
|
||||
return Failure;
|
||||
}
|
||||
|
||||
if (!document.HasMember("stream"))
|
||||
{
|
||||
qDebug() << "[TwitchChannel:refreshLiveStatus] Missing stream in root";
|
||||
return Failure;
|
||||
}
|
||||
|
||||
const auto &stream = document["stream"];
|
||||
|
||||
if (!stream.IsObject())
|
||||
{
|
||||
// Stream is offline (stream is most likely null)
|
||||
this->setLive(false);
|
||||
return Failure;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stream.HasMember("viewers") || !stream.HasMember("game") ||
|
||||
!stream.HasMember("channel") || !stream.HasMember("created_at"))
|
||||
{
|
||||
qDebug()
|
||||
<< "[TwitchChannel:refreshLiveStatus] Missing members in stream";
|
||||
this->setLive(false);
|
||||
return Failure;
|
||||
}
|
||||
|
||||
const rapidjson::Value &streamChannel = stream["channel"];
|
||||
|
||||
if (!streamChannel.IsObject() || !streamChannel.HasMember("status"))
|
||||
{
|
||||
qDebug() << "[TwitchChannel:refreshLiveStatus] Missing member "
|
||||
"\"status\" in channel";
|
||||
return Failure;
|
||||
}
|
||||
|
||||
// Stream is live
|
||||
|
||||
{
|
||||
auto status = this->streamStatus_.access();
|
||||
status->viewerCount = stream["viewers"].GetUint();
|
||||
status->game = stream["game"].GetString();
|
||||
status->title = streamChannel["status"].GetString();
|
||||
QDateTime since = QDateTime::fromString(
|
||||
stream["created_at"].GetString(), Qt::ISODate);
|
||||
status->viewerCount = stream.viewerCount;
|
||||
if (status->gameId != stream.gameId)
|
||||
{
|
||||
status->gameId = stream.gameId;
|
||||
|
||||
// Resolve game ID to game name
|
||||
getHelix()->getGameById(
|
||||
stream.gameId,
|
||||
[this, weak = weakOf<Channel>(this)](const auto &game) {
|
||||
ChannelPtr shared = weak.lock();
|
||||
if (!shared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
auto status = this->streamStatus_.access();
|
||||
status->game = game.name;
|
||||
}
|
||||
|
||||
this->liveStatusChanged.invoke();
|
||||
},
|
||||
[] {
|
||||
// failure
|
||||
});
|
||||
}
|
||||
status->game = stream.gameId;
|
||||
status->title = stream.title;
|
||||
QDateTime since = QDateTime::fromString(stream.startedAt, Qt::ISODate);
|
||||
auto diff = since.secsTo(QDateTime::currentDateTime());
|
||||
status->uptime = QString::number(diff / 3600) + "h " +
|
||||
QString::number(diff % 3600 / 60) + "m";
|
||||
|
||||
status->rerun = false;
|
||||
if (stream.HasMember("stream_type"))
|
||||
{
|
||||
status->streamType = stream["stream_type"].GetString();
|
||||
}
|
||||
else
|
||||
{
|
||||
status->streamType = QString();
|
||||
}
|
||||
|
||||
if (stream.HasMember("broadcast_platform"))
|
||||
{
|
||||
const auto &broadcastPlatformValue = stream["broadcast_platform"];
|
||||
|
||||
if (broadcastPlatformValue.IsString())
|
||||
{
|
||||
const char *broadcastPlatform =
|
||||
stream["broadcast_platform"].GetString();
|
||||
if (strcmp(broadcastPlatform, "rerun") == 0)
|
||||
{
|
||||
status->rerun = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
status->streamType = stream.type;
|
||||
}
|
||||
setLive(true);
|
||||
|
||||
this->setLive(true);
|
||||
|
||||
// Signal all listeners that the stream status has been updated
|
||||
this->liveStatusChanged.invoke();
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
void TwitchChannel::loadRecentMessages()
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
#include "common/UniqueAccess.hpp"
|
||||
#include "common/UsernameSet.hpp"
|
||||
#include "providers/twitch/TwitchEmotes.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <IrcConnection>
|
||||
#include <QColor>
|
||||
#include <QRegularExpression>
|
||||
#include <boost/optional.hpp>
|
||||
#include <mutex>
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace chatterino {
|
||||
@@ -43,6 +44,7 @@ public:
|
||||
unsigned viewerCount = 0;
|
||||
QString title;
|
||||
QString game;
|
||||
QString gameId;
|
||||
QString uptime;
|
||||
QString streamType;
|
||||
};
|
||||
@@ -120,7 +122,7 @@ protected:
|
||||
private:
|
||||
// Methods
|
||||
void refreshLiveStatus();
|
||||
Outcome parseLiveStatus(const rapidjson::Document &document);
|
||||
void parseLiveStatus(bool live, const HelixStream &stream);
|
||||
void refreshPubsub();
|
||||
void refreshChatters();
|
||||
void refreshBadges();
|
||||
|
||||
@@ -278,6 +278,7 @@ namespace {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Look through the results of
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
|
||||
#include "common/Outcome.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
static Helix *instance = nullptr;
|
||||
|
||||
void Helix::fetchUsers(QStringList userIds, QStringList userLogins,
|
||||
ResultCallback<std::vector<HelixUser>> successCallback,
|
||||
HelixFailureCallback failureCallback)
|
||||
{
|
||||
QUrlQuery urlQuery;
|
||||
|
||||
for (const auto &id : userIds)
|
||||
{
|
||||
urlQuery.addQueryItem("id", id);
|
||||
}
|
||||
|
||||
for (const auto &login : userLogins)
|
||||
{
|
||||
urlQuery.addQueryItem("login", login);
|
||||
}
|
||||
|
||||
// TODO: set on success and on error
|
||||
this->makeRequest("users", urlQuery)
|
||||
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||
auto root = result.parseJson();
|
||||
auto data = root.value("data");
|
||||
|
||||
if (!data.isArray())
|
||||
{
|
||||
failureCallback();
|
||||
return Failure;
|
||||
}
|
||||
|
||||
std::vector<HelixUser> users;
|
||||
|
||||
for (const auto &jsonUser : data.toArray())
|
||||
{
|
||||
users.emplace_back(jsonUser.toObject());
|
||||
}
|
||||
|
||||
successCallback(users);
|
||||
|
||||
return Success;
|
||||
})
|
||||
.onError([failureCallback](auto result) {
|
||||
// TODO: make better xd
|
||||
failureCallback();
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
void Helix::getUserByName(QString userId,
|
||||
ResultCallback<HelixUser> successCallback,
|
||||
HelixFailureCallback failureCallback)
|
||||
{
|
||||
QStringList userIds;
|
||||
QStringList userLogins{userId};
|
||||
|
||||
this->fetchUsers(
|
||||
userIds, userLogins,
|
||||
[successCallback,
|
||||
failureCallback](const std::vector<HelixUser> &users) {
|
||||
if (users.empty())
|
||||
{
|
||||
failureCallback();
|
||||
return;
|
||||
}
|
||||
successCallback(users[0]);
|
||||
},
|
||||
failureCallback);
|
||||
}
|
||||
|
||||
void Helix::getUserById(QString userId,
|
||||
ResultCallback<HelixUser> successCallback,
|
||||
HelixFailureCallback failureCallback)
|
||||
{
|
||||
QStringList userIds{userId};
|
||||
QStringList userLogins;
|
||||
|
||||
this->fetchUsers(
|
||||
userIds, userLogins,
|
||||
[successCallback, failureCallback](const auto &users) {
|
||||
if (users.empty())
|
||||
{
|
||||
failureCallback();
|
||||
return;
|
||||
}
|
||||
successCallback(users[0]);
|
||||
},
|
||||
failureCallback);
|
||||
}
|
||||
|
||||
void Helix::fetchUsersFollows(
|
||||
QString fromId, QString toId,
|
||||
ResultCallback<HelixUsersFollowsResponse> successCallback,
|
||||
HelixFailureCallback failureCallback)
|
||||
{
|
||||
assert(!fromId.isEmpty() || !toId.isEmpty());
|
||||
|
||||
QUrlQuery urlQuery;
|
||||
|
||||
if (!fromId.isEmpty())
|
||||
{
|
||||
urlQuery.addQueryItem("from_id", fromId);
|
||||
}
|
||||
|
||||
if (!toId.isEmpty())
|
||||
{
|
||||
urlQuery.addQueryItem("to_id", toId);
|
||||
}
|
||||
|
||||
// TODO: set on success and on error
|
||||
this->makeRequest("users/follows", urlQuery)
|
||||
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||
auto root = result.parseJson();
|
||||
if (root.empty())
|
||||
{
|
||||
failureCallback();
|
||||
return Failure;
|
||||
}
|
||||
successCallback(HelixUsersFollowsResponse(root));
|
||||
return Success;
|
||||
})
|
||||
.onError([failureCallback](auto result) {
|
||||
// TODO: make better xd
|
||||
failureCallback();
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
void Helix::getUserFollowers(
|
||||
QString userId, ResultCallback<HelixUsersFollowsResponse> successCallback,
|
||||
HelixFailureCallback failureCallback)
|
||||
{
|
||||
this->fetchUsersFollows("", userId, successCallback, failureCallback);
|
||||
}
|
||||
|
||||
void Helix::getUserFollow(
|
||||
QString userId, QString targetId,
|
||||
ResultCallback<bool, HelixUsersFollowsRecord> successCallback,
|
||||
HelixFailureCallback failureCallback)
|
||||
{
|
||||
this->fetchUsersFollows(
|
||||
userId, targetId,
|
||||
[successCallback](const auto &response) {
|
||||
if (response.data.empty())
|
||||
{
|
||||
successCallback(false, HelixUsersFollowsRecord());
|
||||
return;
|
||||
}
|
||||
|
||||
successCallback(true, response.data[0]);
|
||||
},
|
||||
failureCallback);
|
||||
}
|
||||
|
||||
void Helix::fetchStreams(
|
||||
QStringList userIds, QStringList userLogins,
|
||||
ResultCallback<std::vector<HelixStream>> successCallback,
|
||||
HelixFailureCallback failureCallback)
|
||||
{
|
||||
QUrlQuery urlQuery;
|
||||
|
||||
for (const auto &id : userIds)
|
||||
{
|
||||
urlQuery.addQueryItem("user_id", id);
|
||||
}
|
||||
|
||||
for (const auto &login : userLogins)
|
||||
{
|
||||
urlQuery.addQueryItem("user_login", login);
|
||||
}
|
||||
|
||||
// TODO: set on success and on error
|
||||
this->makeRequest("streams", urlQuery)
|
||||
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||
auto root = result.parseJson();
|
||||
auto data = root.value("data");
|
||||
|
||||
if (!data.isArray())
|
||||
{
|
||||
failureCallback();
|
||||
return Failure;
|
||||
}
|
||||
|
||||
std::vector<HelixStream> streams;
|
||||
|
||||
for (const auto &jsonStream : data.toArray())
|
||||
{
|
||||
streams.emplace_back(jsonStream.toObject());
|
||||
}
|
||||
|
||||
successCallback(streams);
|
||||
|
||||
return Success;
|
||||
})
|
||||
.onError([failureCallback](auto result) {
|
||||
// TODO: make better xd
|
||||
failureCallback();
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
void Helix::getStreamById(QString userId,
|
||||
ResultCallback<bool, HelixStream> successCallback,
|
||||
HelixFailureCallback failureCallback)
|
||||
{
|
||||
QStringList userIds{userId};
|
||||
QStringList userLogins;
|
||||
|
||||
this->fetchStreams(
|
||||
userIds, userLogins,
|
||||
[successCallback, failureCallback](const auto &streams) {
|
||||
if (streams.empty())
|
||||
{
|
||||
successCallback(false, HelixStream());
|
||||
return;
|
||||
}
|
||||
successCallback(true, streams[0]);
|
||||
},
|
||||
failureCallback);
|
||||
}
|
||||
|
||||
void Helix::getStreamByName(QString userName,
|
||||
ResultCallback<bool, HelixStream> successCallback,
|
||||
HelixFailureCallback failureCallback)
|
||||
{
|
||||
QStringList userIds;
|
||||
QStringList userLogins{userName};
|
||||
|
||||
this->fetchStreams(
|
||||
userIds, userLogins,
|
||||
[successCallback, failureCallback](const auto &streams) {
|
||||
if (streams.empty())
|
||||
{
|
||||
successCallback(false, HelixStream());
|
||||
return;
|
||||
}
|
||||
successCallback(true, streams[0]);
|
||||
},
|
||||
failureCallback);
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
void Helix::fetchGames(QStringList gameIds, QStringList gameNames,
|
||||
ResultCallback<std::vector<HelixGame>> successCallback,
|
||||
HelixFailureCallback failureCallback)
|
||||
{
|
||||
assert((gameIds.length() + gameNames.length()) > 0);
|
||||
|
||||
QUrlQuery urlQuery;
|
||||
|
||||
for (const auto &id : gameIds)
|
||||
{
|
||||
urlQuery.addQueryItem("id", id);
|
||||
}
|
||||
|
||||
for (const auto &login : gameNames)
|
||||
{
|
||||
urlQuery.addQueryItem("name", login);
|
||||
}
|
||||
|
||||
// TODO: set on success and on error
|
||||
this->makeRequest("games", urlQuery)
|
||||
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||
auto root = result.parseJson();
|
||||
auto data = root.value("data");
|
||||
|
||||
if (!data.isArray())
|
||||
{
|
||||
failureCallback();
|
||||
return Failure;
|
||||
}
|
||||
|
||||
std::vector<HelixGame> games;
|
||||
|
||||
for (const auto &jsonStream : data.toArray())
|
||||
{
|
||||
games.emplace_back(jsonStream.toObject());
|
||||
}
|
||||
|
||||
successCallback(games);
|
||||
|
||||
return Success;
|
||||
})
|
||||
.onError([failureCallback](auto result) {
|
||||
// TODO: make better xd
|
||||
failureCallback();
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
void Helix::getGameById(QString gameId,
|
||||
ResultCallback<HelixGame> successCallback,
|
||||
HelixFailureCallback failureCallback)
|
||||
{
|
||||
QStringList gameIds{gameId};
|
||||
QStringList gameNames;
|
||||
|
||||
this->fetchGames(
|
||||
gameIds, gameNames,
|
||||
[successCallback, failureCallback](const auto &games) {
|
||||
if (games.empty())
|
||||
{
|
||||
failureCallback();
|
||||
return;
|
||||
}
|
||||
successCallback(games[0]);
|
||||
},
|
||||
failureCallback);
|
||||
}
|
||||
|
||||
NetworkRequest Helix::makeRequest(QString url, QUrlQuery urlQuery)
|
||||
{
|
||||
assert(!url.startsWith("/"));
|
||||
|
||||
if (this->clientId.isEmpty())
|
||||
{
|
||||
qDebug()
|
||||
<< "Helix::makeRequest called without a client ID set BabyRage";
|
||||
// return boost::none;
|
||||
}
|
||||
|
||||
if (this->oauthToken.isEmpty())
|
||||
{
|
||||
qDebug()
|
||||
<< "Helix::makeRequest called without an oauth token set BabyRage";
|
||||
// return boost::none;
|
||||
}
|
||||
|
||||
const QString baseUrl("https://api.twitch.tv/helix/");
|
||||
|
||||
QUrl fullUrl(baseUrl + url);
|
||||
|
||||
fullUrl.setQuery(urlQuery);
|
||||
|
||||
return NetworkRequest(fullUrl)
|
||||
.timeout(5 * 1000)
|
||||
.header("Accept", "application/json")
|
||||
.header("Client-ID", this->clientId)
|
||||
.header("Authorization", "Bearer " + this->oauthToken);
|
||||
}
|
||||
|
||||
void Helix::update(QString clientId, QString oauthToken)
|
||||
{
|
||||
this->clientId = clientId;
|
||||
this->oauthToken = oauthToken;
|
||||
}
|
||||
|
||||
void Helix::initialize()
|
||||
{
|
||||
assert(instance == nullptr);
|
||||
|
||||
instance = new Helix();
|
||||
}
|
||||
|
||||
Helix *getHelix()
|
||||
{
|
||||
assert(instance != nullptr);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,198 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/NetworkRequest.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
using HelixFailureCallback = std::function<void()>;
|
||||
template <typename... T>
|
||||
using ResultCallback = std::function<void(T...)>;
|
||||
|
||||
struct HelixUser {
|
||||
QString id;
|
||||
QString login;
|
||||
QString displayName;
|
||||
QString description;
|
||||
QString profileImageUrl;
|
||||
int viewCount;
|
||||
|
||||
explicit HelixUser(QJsonObject jsonObject)
|
||||
: id(jsonObject.value("id").toString())
|
||||
, login(jsonObject.value("login").toString())
|
||||
, displayName(jsonObject.value("display_name").toString())
|
||||
, description(jsonObject.value("description").toString())
|
||||
, profileImageUrl(jsonObject.value("profile_image_url").toString())
|
||||
, viewCount(jsonObject.value("view_count").toInt())
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct HelixUsersFollowsRecord {
|
||||
QString fromId;
|
||||
QString fromName;
|
||||
QString toId;
|
||||
QString toName;
|
||||
QString followedAt; // date time object
|
||||
|
||||
HelixUsersFollowsRecord()
|
||||
: fromId("")
|
||||
, fromName("")
|
||||
, toId("")
|
||||
, toName("")
|
||||
, followedAt("")
|
||||
{
|
||||
}
|
||||
|
||||
explicit HelixUsersFollowsRecord(QJsonObject jsonObject)
|
||||
: fromId(jsonObject.value("from_id").toString())
|
||||
, fromName(jsonObject.value("from_name").toString())
|
||||
, toId(jsonObject.value("to_id").toString())
|
||||
, toName(jsonObject.value("to_name").toString())
|
||||
, followedAt(jsonObject.value("followed_at").toString())
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct HelixUsersFollowsResponse {
|
||||
int total;
|
||||
std::vector<HelixUsersFollowsRecord> data;
|
||||
explicit HelixUsersFollowsResponse(QJsonObject jsonObject)
|
||||
: total(jsonObject.value("total").toInt())
|
||||
{
|
||||
const auto &jsonData = jsonObject.value("data").toArray();
|
||||
std::transform(jsonData.begin(), jsonData.end(),
|
||||
std::back_inserter(this->data),
|
||||
[](const QJsonValue &record) {
|
||||
return HelixUsersFollowsRecord(record.toObject());
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
struct HelixStream {
|
||||
QString id; // stream id
|
||||
QString userId;
|
||||
QString userName;
|
||||
QString gameId;
|
||||
QString type;
|
||||
QString title;
|
||||
int viewerCount;
|
||||
QString startedAt;
|
||||
QString language;
|
||||
QString thumbnailUrl;
|
||||
|
||||
HelixStream()
|
||||
: id("")
|
||||
, userId("")
|
||||
, userName("")
|
||||
, gameId("")
|
||||
, type("")
|
||||
, title("")
|
||||
, viewerCount()
|
||||
, startedAt("")
|
||||
, language("")
|
||||
, thumbnailUrl("")
|
||||
{
|
||||
}
|
||||
|
||||
explicit HelixStream(QJsonObject jsonObject)
|
||||
: id(jsonObject.value("id").toString())
|
||||
, userId(jsonObject.value("user_id").toString())
|
||||
, userName(jsonObject.value("user_name").toString())
|
||||
, gameId(jsonObject.value("game_id").toString())
|
||||
, type(jsonObject.value("type").toString())
|
||||
, title(jsonObject.value("title").toString())
|
||||
, viewerCount(jsonObject.value("viewer_count").toInt())
|
||||
, startedAt(jsonObject.value("started_at").toString())
|
||||
, language(jsonObject.value("language").toString())
|
||||
, thumbnailUrl(jsonObject.value("thumbnail_url").toString())
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct HelixGame {
|
||||
QString id; // stream id
|
||||
QString name;
|
||||
QString boxArtUrl;
|
||||
|
||||
explicit HelixGame(QJsonObject jsonObject)
|
||||
: id(jsonObject.value("id").toString())
|
||||
, name(jsonObject.value("name").toString())
|
||||
, boxArtUrl(jsonObject.value("box_art_url").toString())
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class Helix final : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
// https://dev.twitch.tv/docs/api/reference#get-users
|
||||
void fetchUsers(QStringList userIds, QStringList userLogins,
|
||||
ResultCallback<std::vector<HelixUser>> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
void getUserByName(QString userName,
|
||||
ResultCallback<HelixUser> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
void getUserById(QString userId, ResultCallback<HelixUser> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
// https://dev.twitch.tv/docs/api/reference#get-users-follows
|
||||
void fetchUsersFollows(
|
||||
QString fromId, QString toId,
|
||||
ResultCallback<HelixUsersFollowsResponse> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
void getUserFollowers(
|
||||
QString userId,
|
||||
ResultCallback<HelixUsersFollowsResponse> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
void getUserFollow(
|
||||
QString userId, QString targetId,
|
||||
ResultCallback<bool, HelixUsersFollowsRecord> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
// https://dev.twitch.tv/docs/api/reference#get-streams
|
||||
void fetchStreams(QStringList userIds, QStringList userLogins,
|
||||
ResultCallback<std::vector<HelixStream>> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
void getStreamById(QString userId,
|
||||
ResultCallback<bool, HelixStream> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
void getStreamByName(QString userName,
|
||||
ResultCallback<bool, HelixStream> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
// https://dev.twitch.tv/docs/api/reference#get-games
|
||||
void fetchGames(QStringList gameIds, QStringList gameNames,
|
||||
ResultCallback<std::vector<HelixGame>> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
void getGameById(QString gameId, ResultCallback<HelixGame> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
void update(QString clientId, QString oauthToken);
|
||||
|
||||
static void initialize();
|
||||
|
||||
private:
|
||||
NetworkRequest makeRequest(QString url, QUrlQuery urlQuery);
|
||||
|
||||
QString clientId;
|
||||
QString oauthToken;
|
||||
};
|
||||
|
||||
Helix *getHelix();
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "providers/twitch/api/Kraken.hpp"
|
||||
|
||||
#include "common/Outcome.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
static Kraken *instance = nullptr;
|
||||
|
||||
void Kraken::getChannel(QString userId,
|
||||
ResultCallback<KrakenChannel> successCallback,
|
||||
KrakenFailureCallback failureCallback)
|
||||
{
|
||||
assert(!userId.isEmpty());
|
||||
|
||||
this->makeRequest("channels/" + userId, {})
|
||||
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||
auto root = result.parseJson();
|
||||
|
||||
successCallback(root);
|
||||
|
||||
return Success;
|
||||
})
|
||||
.onError([failureCallback](auto result) {
|
||||
// TODO: make better xd
|
||||
failureCallback();
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
void Kraken::getUser(QString userId, ResultCallback<KrakenUser> successCallback,
|
||||
KrakenFailureCallback failureCallback)
|
||||
{
|
||||
assert(!userId.isEmpty());
|
||||
|
||||
this->makeRequest("users/" + userId, {})
|
||||
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||
auto root = result.parseJson();
|
||||
|
||||
successCallback(root);
|
||||
|
||||
return Success;
|
||||
})
|
||||
.onError([failureCallback](auto result) {
|
||||
// TODO: make better xd
|
||||
failureCallback();
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
NetworkRequest Kraken::makeRequest(QString url, QUrlQuery urlQuery)
|
||||
{
|
||||
assert(!url.startsWith("/"));
|
||||
|
||||
if (this->clientId.isEmpty())
|
||||
{
|
||||
qDebug()
|
||||
<< "Kraken::makeRequest called without a client ID set BabyRage";
|
||||
}
|
||||
|
||||
const QString baseUrl("https://api.twitch.tv/kraken/");
|
||||
|
||||
QUrl fullUrl(baseUrl + url);
|
||||
|
||||
fullUrl.setQuery(urlQuery);
|
||||
|
||||
if (!this->oauthToken.isEmpty())
|
||||
{
|
||||
return NetworkRequest(fullUrl)
|
||||
.timeout(5 * 1000)
|
||||
.header("Accept", "application/vnd.twitchtv.v5+json")
|
||||
.header("Client-ID", this->clientId)
|
||||
.header("Authorization", "OAuth " + this->oauthToken);
|
||||
}
|
||||
|
||||
return NetworkRequest(fullUrl)
|
||||
.timeout(5 * 1000)
|
||||
.header("Accept", "application/vnd.twitchtv.v5+json")
|
||||
.header("Client-ID", this->clientId);
|
||||
}
|
||||
|
||||
void Kraken::update(QString clientId, QString oauthToken)
|
||||
{
|
||||
this->clientId = clientId;
|
||||
this->oauthToken = oauthToken;
|
||||
}
|
||||
|
||||
void Kraken::initialize()
|
||||
{
|
||||
assert(instance == nullptr);
|
||||
|
||||
instance = new Kraken();
|
||||
|
||||
getKraken()->update(getDefaultClientID(), "");
|
||||
}
|
||||
|
||||
Kraken *getKraken()
|
||||
{
|
||||
assert(instance != nullptr);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/NetworkRequest.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrlQuery>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
using KrakenFailureCallback = std::function<void()>;
|
||||
template <typename... T>
|
||||
using ResultCallback = std::function<void(T...)>;
|
||||
|
||||
struct KrakenChannel {
|
||||
const QString status;
|
||||
|
||||
KrakenChannel(QJsonObject jsonObject)
|
||||
: status(jsonObject.value("status").toString())
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct KrakenUser {
|
||||
const QString createdAt;
|
||||
|
||||
KrakenUser(QJsonObject jsonObject)
|
||||
: createdAt(jsonObject.value("created_at").toString())
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class Kraken final : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
// https://dev.twitch.tv/docs/v5/reference/users#follow-channel
|
||||
void getChannel(QString userId,
|
||||
ResultCallback<KrakenChannel> resultCallback,
|
||||
KrakenFailureCallback failureCallback);
|
||||
|
||||
// https://dev.twitch.tv/docs/v5/reference/users#get-user-by-id
|
||||
void getUser(QString userId, ResultCallback<KrakenUser> resultCallback,
|
||||
KrakenFailureCallback failureCallback);
|
||||
|
||||
void update(QString clientId, QString oauthToken);
|
||||
|
||||
static void initialize();
|
||||
|
||||
private:
|
||||
NetworkRequest makeRequest(QString url, QUrlQuery urlQuery);
|
||||
|
||||
QString clientId;
|
||||
QString oauthToken;
|
||||
};
|
||||
|
||||
Kraken *getKraken();
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,125 @@
|
||||
# Twitch API
|
||||
this folder describes what sort of API requests we do, what permissions are required for the requests etc
|
||||
|
||||
## Kraken (V5)
|
||||
We use a bunch of Kraken (V5) in Chatterino2.
|
||||
|
||||
### Get User
|
||||
URL: https://dev.twitch.tv/docs/v5/reference/users#get-user-by-id
|
||||
|
||||
Migration path: **Unknown**
|
||||
|
||||
* We implement this in `providers/twitch/api/Kraken.cpp getUser`
|
||||
Used in:
|
||||
* `UserInfoPopup` to get the "created at" date of a user
|
||||
|
||||
### Get Channel
|
||||
URL: https://dev.twitch.tv/docs/v5/reference/channels#get-channel
|
||||
|
||||
Migration path: **Unknown**
|
||||
|
||||
* We implement this in `providers/twitch/api/Kraken.cpp getChannel`
|
||||
Used in:
|
||||
* `TwitchChannel::refreshTitle` to check the current stream title/game of offline channels
|
||||
|
||||
### Follow Channel
|
||||
URL: https://dev.twitch.tv/docs/v5/reference/users#follow-channel
|
||||
Requires `user_follows_edit` scope
|
||||
|
||||
Migration path: **Unknown**
|
||||
|
||||
* We implement this API in `providers/twitch/TwitchAccount.cpp followUser`
|
||||
|
||||
### Unfollow Channel
|
||||
URL: https://dev.twitch.tv/docs/v5/reference/users#unfollow-channel
|
||||
Requires `user_follows_edit` scope
|
||||
|
||||
Migration path: **Unknown**
|
||||
|
||||
* We implement this API in `providers/twitch/TwitchAccount.cpp unfollowUser`
|
||||
|
||||
|
||||
### 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 Block List
|
||||
URL: https://dev.twitch.tv/docs/v5/reference/users#get-user-block-list
|
||||
|
||||
Migration path: **Unknown**
|
||||
|
||||
* We use this in `providers/twitch/TwitchAccount.cpp loadIgnores`
|
||||
|
||||
### Block User
|
||||
URL: https://dev.twitch.tv/docs/v5/reference/users#block-user
|
||||
Requires `user_blocks_edit` scope
|
||||
|
||||
Migration path: **Unknown**
|
||||
|
||||
* We use this in `providers/twitch/TwitchAccount.cpp ignoreByID`
|
||||
|
||||
### Unblock User
|
||||
URL: https://dev.twitch.tv/docs/v5/reference/users#unblock-user
|
||||
Requires `user_blocks_edit` scope
|
||||
|
||||
Migration path: **Unknown**
|
||||
|
||||
* We use this in `providers/twitch/TwitchAccount.cpp unignoreByID`
|
||||
|
||||
### Get User Emotes
|
||||
URL: https://dev.twitch.tv/docs/v5/reference/users#get-user-emotes
|
||||
Requires `user_subscriptions` scope
|
||||
|
||||
Migration path: **Unknown**
|
||||
|
||||
* We use this in `providers/twitch/TwitchAccount.cpp loadEmotes` to figure out which emotes a user is allowed to use!
|
||||
|
||||
### AUTOMOD APPROVE
|
||||
**Unofficial** documentation: https://discuss.dev.twitch.tv/t/allowing-others-aka-bots-to-use-twitchbot-reject/8508/2
|
||||
|
||||
* We use this in `providers/twitch/TwitchAccount.cpp autoModAllow` to approve an automod deny/allow question
|
||||
|
||||
### AUTOMOD DENY
|
||||
**Unofficial** documentation: https://discuss.dev.twitch.tv/t/allowing-others-aka-bots-to-use-twitchbot-reject/8508/2
|
||||
|
||||
* We use this in `providers/twitch/TwitchAccount.cpp autoModDeny` to deny an automod deny/allow question
|
||||
|
||||
## Helix
|
||||
Full Helix API reference: https://dev.twitch.tv/docs/api/reference
|
||||
|
||||
### Get Users
|
||||
URL: https://dev.twitch.tv/docs/api/reference#get-users
|
||||
|
||||
* We implement this in `providers/twitch/api/Helix.cpp fetchUsers`.
|
||||
Used in:
|
||||
* `UserInfoPopup` to get ID and viewcount of username we clicked
|
||||
* `CommandController` to power any commands that need to get a user ID
|
||||
* `Toasts` to get the profile picture of a streamer who just went live
|
||||
* `TwitchAccount` ignore and unignore features to translate user name to user ID
|
||||
|
||||
### Get Users Follows
|
||||
URL: https://dev.twitch.tv/docs/api/reference#get-users-follows
|
||||
|
||||
* We implement this in `providers/twitch/api/Helix.cpp fetchUsersFollows`
|
||||
Used in:
|
||||
* `UserInfoPopup` to get number of followers a user has
|
||||
|
||||
### Get Streams
|
||||
URL: https://dev.twitch.tv/docs/api/reference#get-streams
|
||||
|
||||
* We implement this in `providers/twitch/api/Helix.cpp fetchStreams`
|
||||
Used in:
|
||||
* `TwitchChannel` to get live status, game, title, and viewer count of a channel
|
||||
* `NotificationController` to provide notifications for channels you might not have open in Chatterino, but are still interested in getting notifications for
|
||||
|
||||
## TMI
|
||||
The TMI api is undocumented.
|
||||
|
||||
### Get Chatters
|
||||
**Undocumented**
|
||||
|
||||
* We use this in `widgets/splits/Split.cpp showViewerList`
|
||||
* We use this in `providers/twitch/TwitchChannel.cpp refreshChatters`
|
||||
Reference in New Issue
Block a user