#include "providers/twitch/api/Helix.hpp" #include "common/Outcome.hpp" #include "common/QLogging.hpp" namespace chatterino { static Helix *instance = nullptr; void Helix::fetchUsers(QStringList userIds, QStringList userLogins, ResultCallback> 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 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 userName, ResultCallback successCallback, HelixFailureCallback failureCallback) { QStringList userIds; QStringList userLogins{userName}; this->fetchUsers( userIds, userLogins, [successCallback, failureCallback](const std::vector &users) { if (users.empty()) { failureCallback(); return; } successCallback(users[0]); }, failureCallback); } void Helix::getUserById(QString userId, ResultCallback 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 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 successCallback, HelixFailureCallback failureCallback) { this->fetchUsersFollows("", userId, successCallback, failureCallback); } void Helix::getUserFollow( QString userId, QString targetId, ResultCallback 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> 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 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 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 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> 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 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 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); } void Helix::followUser(QString userId, QString targetId, std::function 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 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(); } void Helix::createClip(QString channelId, ResultCallback successCallback, std::function failureCallback, std::function 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(); } NetworkRequest Helix::makeRequest(QString url, QUrlQuery urlQuery) { assert(!url.startsWith("/")); if (this->clientId.isEmpty()) { qCDebug(chatterinoTwitch) << "Helix::makeRequest called without a client ID set BabyRage"; // return boost::none; } if (this->oauthToken.isEmpty()) { qCDebug(chatterinoTwitch) << "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