From 33ab6149bf42bca0a4da7d62cee2e48c8753d1b1 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 11 Nov 2023 12:09:31 +0100 Subject: [PATCH] Add support for the Create EventSub Subscription Helix API --- mocks/include/mocks/Helix.hpp | 9 +++ src/providers/twitch/api/Helix.cpp | 109 +++++++++++++++++++++++++++++ src/providers/twitch/api/Helix.hpp | 75 ++++++++++++++++++++ 3 files changed, 193 insertions(+) diff --git a/mocks/include/mocks/Helix.hpp b/mocks/include/mocks/Helix.hpp index 1771b1e2b..7a1230912 100644 --- a/mocks/include/mocks/Helix.hpp +++ b/mocks/include/mocks/Helix.hpp @@ -392,6 +392,15 @@ public: (FailureCallback failureCallback)), (override)); + MOCK_METHOD(void, createEventSubSubscription, + (const QString &type, const QString &version, + const QString &sessionID, const QJsonObject &condition, + ResultCallback + successCallback, + (FailureCallback + failureCallback)), + (override)); + MOCK_METHOD(void, update, (QString clientId, QString oauthToken), (override)); diff --git a/src/providers/twitch/api/Helix.cpp b/src/providers/twitch/api/Helix.cpp index 7fd2cf1ff..4fe528852 100644 --- a/src/providers/twitch/api/Helix.cpp +++ b/src/providers/twitch/api/Helix.cpp @@ -2836,6 +2836,115 @@ void Helix::sendShoutout( .execute(); } +void Helix::createEventSubSubscription( + const QString &type, const QString &version, const QString &sessionID, + const QJsonObject &condition, + ResultCallback successCallback, + FailureCallback + failureCallback) +{ + using Error = HelixCreateEventSubSubscriptionError; + + QJsonObject body; + body.insert("type", type); + body.insert("version", version); + body.insert("condition", condition); + + QJsonObject transport; + transport.insert("method", "websocket"); + transport.insert("session_id", sessionID); + + body.insert("transport", transport); + + this->makePost("eventsub/subscriptions", {}) + .json(body) + .onSuccess([successCallback](const auto &result) { + if (result.status() != 202) + { + qCWarning(chatterinoTwitch) + << "Success result for creating eventsub subscription was " + << result.formatError() << "but we expected it to be 202"; + } + + HelixCreateEventSubSubscriptionResponse response( + result.parseJson()); + + successCallback(response); + }) + .onError([failureCallback](const NetworkResult &result) { + if (!result.status()) + { + failureCallback(Error::Forwarded, result.formatError()); + return; + } + + const auto obj = result.parseJson(); + auto message = obj["message"].toString(); + + switch (*result.status()) + { + case 400: { + failureCallback(Error::BadRequest, message); + } + break; + + case 401: { + failureCallback(Error::Unauthorized, message); + } + break; + + case 403: { + failureCallback(Error::Forbidden, message); + } + break; + + case 429: { + failureCallback(Error::Ratelimited, message); + } + break; + + case 500: { + if (message.isEmpty()) + { + failureCallback(Error::Forwarded, + "Twitch internal server error"); + } + else + { + failureCallback(Error::Forwarded, message); + } + } + break; + + default: { + qCWarning(chatterinoTwitch) + << "Helix Create EventSub Subscription, unhandled " + "error data:" + << result.formatError() << result.getData() << obj; + failureCallback(Error::Forwarded, message); + } + } + }) + .execute(); +} + +QDebug &operator<<(QDebug &dbg, + const HelixCreateEventSubSubscriptionResponse &data) +{ + dbg << "HelixCreateEventSubSubscriptionResponse{ id:" << data.subscriptionID + << "status:" << data.subscriptionStatus + << "type:" << data.subscriptionType + << "version:" << data.subscriptionVersion + << "condition:" << data.subscriptionCondition + << "createdAt:" << data.subscriptionCreatedAt + << "sessionID:" << data.subscriptionSessionID + << "connectedAt:" << data.subscriptionConnectedAt + << "cost:" << data.subscriptionCost << "total:" << data.total + << "totalCost:" << data.totalCost + << "maxTotalCost:" << data.maxTotalCost << '}'; + return dbg; +} + NetworkRequest Helix::makeRequest(const QString &url, const QUrlQuery &urlQuery, NetworkRequestType type) { diff --git a/src/providers/twitch/api/Helix.hpp b/src/providers/twitch/api/Helix.hpp index 256680994..6028935a3 100644 --- a/src/providers/twitch/api/Helix.hpp +++ b/src/providers/twitch/api/Helix.hpp @@ -706,6 +706,65 @@ struct HelixError { using HelixGetChannelBadgesError = HelixGetGlobalBadgesError; +struct HelixCreateEventSubSubscriptionResponse { + QString subscriptionID; + QString subscriptionStatus; + QString subscriptionType; + QString subscriptionVersion; + QJsonObject subscriptionCondition; + QString subscriptionCreatedAt; + QString subscriptionSessionID; + QString subscriptionConnectedAt; + int subscriptionCost; + + int total; + int totalCost; + int maxTotalCost; + + explicit HelixCreateEventSubSubscriptionResponse( + const QJsonObject &jsonObject) + { + { + auto jsonData = jsonObject.value("data").toArray().at(0).toObject(); + this->subscriptionID = jsonData.value("id").toString(); + this->subscriptionStatus = jsonData.value("status").toString(); + this->subscriptionType = jsonData.value("type").toString(); + this->subscriptionVersion = jsonData.value("version").toString(); + this->subscriptionCondition = + jsonData.value("condition").toObject(); + this->subscriptionCreatedAt = + jsonData.value("created_at").toString(); + this->subscriptionSessionID = jsonData.value("transport") + .toObject() + .value("session_id") + .toString(); + this->subscriptionConnectedAt = jsonData.value("transport") + .toObject() + .value("connected_at") + .toString(); + this->subscriptionCost = jsonData.value("cost").toInt(); + } + + this->total = jsonObject.value("total").toInt(); + this->totalCost = jsonObject.value("total_cost").toInt(); + this->maxTotalCost = jsonObject.value("max_total_cost").toInt(); + } + + friend QDebug &operator<<( + QDebug &dbg, const HelixCreateEventSubSubscriptionResponse &data); +}; + +enum class HelixCreateEventSubSubscriptionError { + BadRequest, + Unauthorized, + Forbidden, + Conflict, + Ratelimited, + + // The error message is forwarded directly from the Twitch API + Forwarded, +}; + class IHelix { public: @@ -1019,6 +1078,14 @@ public: ResultCallback<> successCallback, FailureCallback failureCallback) = 0; + // https://dev.twitch.tv/docs/api/reference/#create-eventsub-subscription + virtual void createEventSubSubscription( + const QString &type, const QString &version, const QString &sessionID, + const QJsonObject &condition, + ResultCallback successCallback, + FailureCallback + failureCallback) = 0; + virtual void update(QString clientId, QString oauthToken) = 0; protected: @@ -1333,6 +1400,14 @@ public: ResultCallback<> successCallback, FailureCallback failureCallback) final; + // https://dev.twitch.tv/docs/api/reference/#create-eventsub-subscription + void createEventSubSubscription( + const QString &type, const QString &version, const QString &sessionID, + const QJsonObject &condition, + ResultCallback successCallback, + FailureCallback + failureCallback) final; + void update(QString clientId, QString oauthToken) final; static void initialize();