2020-03-14 12:13:57 +01:00
|
|
|
#include "providers/twitch/api/Helix.hpp"
|
|
|
|
|
|
|
|
#include "common/Outcome.hpp"
|
2020-11-21 16:20:10 +01:00
|
|
|
#include "common/QLogging.hpp"
|
2020-03-14 12:13:57 +01:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2020-10-04 18:32:52 +02:00
|
|
|
void Helix::getUserByName(QString userName,
|
2020-03-14 12:13:57 +01:00
|
|
|
ResultCallback<HelixUser> successCallback,
|
|
|
|
HelixFailureCallback failureCallback)
|
|
|
|
{
|
|
|
|
QStringList userIds;
|
2020-10-04 18:32:52 +02:00
|
|
|
QStringList userLogins{userName};
|
2020-03-14 12:13:57 +01:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-12-22 09:55:58 +01:00
|
|
|
void Helix::followUser(QString userId, QString targetId,
|
|
|
|
std::function<void()> successCallback,
|
|
|
|
HelixFailureCallback failureCallback)
|
|
|
|
{
|
|
|
|
QUrlQuery urlQuery;
|
|
|
|
|
|
|
|
urlQuery.addQueryItem("from_id", userId);
|
|
|
|
urlQuery.addQueryItem("to_id", targetId);
|
|
|
|
|
|
|
|
this->makeRequest("users/follows", urlQuery)
|
|
|
|
.type(NetworkRequestType::Post)
|
|
|
|
.onSuccess([successCallback](auto result) -> Outcome {
|
|
|
|
successCallback();
|
|
|
|
return Success;
|
|
|
|
})
|
|
|
|
.onError([failureCallback](auto result) {
|
|
|
|
// TODO: make better xd
|
|
|
|
failureCallback();
|
|
|
|
})
|
|
|
|
.execute();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Helix::unfollowUser(QString userId, QString targetId,
|
|
|
|
std::function<void()> successCallback,
|
|
|
|
HelixFailureCallback failureCallback)
|
|
|
|
{
|
|
|
|
QUrlQuery urlQuery;
|
|
|
|
|
|
|
|
urlQuery.addQueryItem("from_id", userId);
|
|
|
|
urlQuery.addQueryItem("to_id", targetId);
|
|
|
|
|
|
|
|
this->makeRequest("users/follows", urlQuery)
|
|
|
|
.type(NetworkRequestType::Delete)
|
|
|
|
.onSuccess([successCallback](auto result) -> Outcome {
|
|
|
|
successCallback();
|
|
|
|
return Success;
|
|
|
|
})
|
|
|
|
.onError([failureCallback](auto result) {
|
|
|
|
// TODO: make better xd
|
|
|
|
failureCallback();
|
|
|
|
})
|
|
|
|
.execute();
|
|
|
|
}
|
|
|
|
|
2021-01-17 14:47:34 +01:00
|
|
|
void Helix::createClip(QString channelId,
|
|
|
|
ResultCallback<HelixClip> successCallback,
|
|
|
|
std::function<void(HelixClipError)> failureCallback,
|
|
|
|
std::function<void()> finallyCallback)
|
|
|
|
{
|
|
|
|
QUrlQuery urlQuery;
|
|
|
|
urlQuery.addQueryItem("broadcaster_id", channelId);
|
|
|
|
|
|
|
|
this->makeRequest("clips", urlQuery)
|
|
|
|
.type(NetworkRequestType::Post)
|
|
|
|
.header("Content-Type", "application/json")
|
|
|
|
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
|
|
|
auto root = result.parseJson();
|
|
|
|
auto data = root.value("data");
|
|
|
|
|
|
|
|
if (!data.isArray())
|
|
|
|
{
|
|
|
|
failureCallback(HelixClipError::Unknown);
|
|
|
|
return Failure;
|
|
|
|
}
|
|
|
|
|
|
|
|
HelixClip clip(data.toArray()[0].toObject());
|
|
|
|
|
|
|
|
successCallback(clip);
|
|
|
|
return Success;
|
|
|
|
})
|
|
|
|
.onError([failureCallback](NetworkResult result) {
|
|
|
|
switch (result.status())
|
|
|
|
{
|
|
|
|
case 503: {
|
|
|
|
// Channel has disabled clip-creation, or channel has made cliops only creatable by followers and the user is not a follower (or subscriber)
|
|
|
|
failureCallback(HelixClipError::ClipsDisabled);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 401: {
|
|
|
|
// User does not have the required scope to be able to create clips, user must reauthenticate
|
|
|
|
failureCallback(HelixClipError::UserNotAuthenticated);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: {
|
|
|
|
qCDebug(chatterinoTwitch)
|
|
|
|
<< "Failed to create a clip: " << result.status()
|
|
|
|
<< result.getData();
|
|
|
|
failureCallback(HelixClipError::Unknown);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.finally(finallyCallback)
|
|
|
|
.execute();
|
|
|
|
}
|
|
|
|
|
2020-03-14 12:13:57 +01:00
|
|
|
NetworkRequest Helix::makeRequest(QString url, QUrlQuery urlQuery)
|
|
|
|
{
|
|
|
|
assert(!url.startsWith("/"));
|
|
|
|
|
|
|
|
if (this->clientId.isEmpty())
|
|
|
|
{
|
2020-11-21 16:20:10 +01:00
|
|
|
qCDebug(chatterinoTwitch)
|
2020-03-14 12:13:57 +01:00
|
|
|
<< "Helix::makeRequest called without a client ID set BabyRage";
|
|
|
|
// return boost::none;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this->oauthToken.isEmpty())
|
|
|
|
{
|
2020-11-21 16:20:10 +01:00
|
|
|
qCDebug(chatterinoTwitch)
|
2020-03-14 12:13:57 +01:00
|
|
|
<< "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
|