feat: add /lockprediction and /cancelprediction commands for broadcasters (#6612)
Reviewed-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -13,6 +13,7 @@ Checks: "-*,
|
|||||||
bugprone-*,
|
bugprone-*,
|
||||||
cert-*,
|
cert-*,
|
||||||
clazy-*,
|
clazy-*,
|
||||||
|
-clazy-qstring-allocations,
|
||||||
cppcoreguidelines-*,
|
cppcoreguidelines-*,
|
||||||
-cppcoreguidelines-pro-type-cstyle-cast,
|
-cppcoreguidelines-pro-type-cstyle-cast,
|
||||||
-cppcoreguidelines-pro-type-vararg,
|
-cppcoreguidelines-pro-type-vararg,
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@
|
|||||||
- Minor: Added a setting to show the stream title in live messages. (#6572)
|
- Minor: Added a setting to show the stream title in live messages. (#6572)
|
||||||
- Minor: Add categories to the emoji viewer. (#6598)
|
- Minor: Add categories to the emoji viewer. (#6598)
|
||||||
- Minor: Added broadcaster-only `/poll`, `/cancelpoll`, and `/endpoll` commands. (#6583, #6605)
|
- Minor: Added broadcaster-only `/poll`, `/cancelpoll`, and `/endpoll` commands. (#6583, #6605)
|
||||||
- Minor: Added broadcaster-only `/prediction` command to start a prediction. (#6583)
|
- Minor: Added broadcaster-only `/prediction`, `/cancelprediction`, and `/lockprediction` commands. (#6583, #6612)
|
||||||
- Bugfix: Expose the "Extra extension IDs" setting on non-Windows systems too. (#6509)
|
- Bugfix: Expose the "Extra extension IDs" setting on non-Windows systems too. (#6509)
|
||||||
- Bugfix: Fixed some commands and filters not working as expected in seach popups. (#6539)
|
- Bugfix: Fixed some commands and filters not working as expected in seach popups. (#6539)
|
||||||
- Bugfix: Fixed settings occasionally not opening when clicking on "Manage Accounts" in the account switcher. (#6543)
|
- Bugfix: Fixed settings occasionally not opening when clicking on "Manage Accounts" in the account switcher. (#6543)
|
||||||
|
|||||||
@@ -459,6 +459,22 @@ public:
|
|||||||
FailureCallback<QString> failureCallback),
|
FailureCallback<QString> failureCallback),
|
||||||
(override));
|
(override));
|
||||||
|
|
||||||
|
// get predictions
|
||||||
|
MOCK_METHOD(void, getPredictions,
|
||||||
|
(QString broadcasterID, QStringList ids, int first,
|
||||||
|
QString after,
|
||||||
|
ResultCallback<HelixPredictions> successCallback,
|
||||||
|
FailureCallback<QString> failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// end prediction
|
||||||
|
MOCK_METHOD(void, endPrediction,
|
||||||
|
(QString broadcasterID, QString id, bool refundPoints,
|
||||||
|
QString winningOutcomeID,
|
||||||
|
ResultCallback<HelixPrediction> successCallback,
|
||||||
|
FailureCallback<QString> failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
MOCK_METHOD(void, createEventSubSubscription,
|
MOCK_METHOD(void, createEventSubSubscription,
|
||||||
(const eventsub::SubscriptionRequest &request,
|
(const eventsub::SubscriptionRequest &request,
|
||||||
const QString &sessionID,
|
const QString &sessionID,
|
||||||
|
|||||||
@@ -488,6 +488,8 @@ CommandController::CommandController(const Paths &paths)
|
|||||||
this->registerCommand("/endpoll", &commands::endPoll);
|
this->registerCommand("/endpoll", &commands::endPoll);
|
||||||
|
|
||||||
this->registerCommand("/prediction", &commands::createPrediction);
|
this->registerCommand("/prediction", &commands::createPrediction);
|
||||||
|
this->registerCommand("/lockprediction", &commands::lockPrediction);
|
||||||
|
this->registerCommand("/cancelprediction", &commands::cancelPrediction);
|
||||||
|
|
||||||
this->registerCommand("/c2-set-logging-rules", &commands::setLoggingRules);
|
this->registerCommand("/c2-set-logging-rules", &commands::setLoggingRules);
|
||||||
this->registerCommand("/c2-theme-autoreload", &commands::toggleThemeReload);
|
this->registerCommand("/c2-theme-autoreload", &commands::toggleThemeReload);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "providers/twitch/api/Helix.hpp"
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
#include "providers/twitch/TwitchAccount.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
|
#include "util/Helpers.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
@@ -66,4 +67,155 @@ QString createPrediction(const CommandContext &ctx)
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString lockPrediction(const CommandContext &ctx)
|
||||||
|
{
|
||||||
|
if (ctx.twitchChannel == nullptr)
|
||||||
|
{
|
||||||
|
const auto err = QStringLiteral(
|
||||||
|
"The /lockprediction command only works in Twitch channels");
|
||||||
|
if (ctx.channel != nullptr)
|
||||||
|
{
|
||||||
|
ctx.channel->addSystemMessage(err);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoCommands) << "Invalid command context:" << err;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
|
||||||
|
if (currentUser->isAnon())
|
||||||
|
{
|
||||||
|
ctx.channel->addSystemMessage(
|
||||||
|
"You must be logged in to lock a prediction!");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto roomId = ctx.twitchChannel->roomId();
|
||||||
|
getHelix()->getPredictions(
|
||||||
|
roomId, {}, 1, {},
|
||||||
|
[channel = ctx.channel, roomId](const auto &result) {
|
||||||
|
if (result.predictions.empty())
|
||||||
|
{
|
||||||
|
channel->addSystemMessage("Failed to find any predictions");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto prediction = result.predictions.front();
|
||||||
|
if (prediction.status == "LOCKED")
|
||||||
|
{
|
||||||
|
channel->addSystemMessage(
|
||||||
|
"The current prediction is already locked: " +
|
||||||
|
prediction.title);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prediction.status != "ACTIVE")
|
||||||
|
{
|
||||||
|
channel->addSystemMessage(
|
||||||
|
"Could not find an active prediction");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHelix()->endPrediction(
|
||||||
|
roomId, prediction.id, false, {},
|
||||||
|
[channel](const HelixPrediction &data) {
|
||||||
|
int totalPoints = 0;
|
||||||
|
int numUsers = 0;
|
||||||
|
for (const auto &outcome : data.outcomes)
|
||||||
|
{
|
||||||
|
totalPoints += outcome.channelPoints;
|
||||||
|
numUsers += outcome.users;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel->addSystemMessage(
|
||||||
|
QString("Locked prediction with %1 points wagered by "
|
||||||
|
"%2 users: '%3'")
|
||||||
|
.arg(localizeNumbers(totalPoints),
|
||||||
|
localizeNumbers(numUsers), data.title));
|
||||||
|
},
|
||||||
|
[channel](const auto &error) {
|
||||||
|
channel->addSystemMessage("Failed to lock prediction - " +
|
||||||
|
error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[channel = ctx.channel](const auto &error) {
|
||||||
|
channel->addSystemMessage("Failed to query predictions - " + error);
|
||||||
|
});
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString cancelPrediction(const CommandContext &ctx)
|
||||||
|
{
|
||||||
|
if (ctx.twitchChannel == nullptr)
|
||||||
|
{
|
||||||
|
const auto err = QStringLiteral(
|
||||||
|
"The /cancelprediction command only works in Twitch channels");
|
||||||
|
if (ctx.channel != nullptr)
|
||||||
|
{
|
||||||
|
ctx.channel->addSystemMessage(err);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoCommands) << "Invalid command context:" << err;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
|
||||||
|
if (currentUser->isAnon())
|
||||||
|
{
|
||||||
|
ctx.channel->addSystemMessage(
|
||||||
|
"You must be logged in to cancel a prediction!");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto roomId = ctx.twitchChannel->roomId();
|
||||||
|
getHelix()->getPredictions(
|
||||||
|
roomId, {}, 1, {},
|
||||||
|
[channel = ctx.channel, roomId](const auto &result) {
|
||||||
|
if (result.predictions.empty())
|
||||||
|
{
|
||||||
|
channel->addSystemMessage("Failed to find any predictions");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto prediction = result.predictions.front();
|
||||||
|
if (prediction.status != "ACTIVE" && prediction.status != "LOCKED")
|
||||||
|
{
|
||||||
|
channel->addSystemMessage("Could not find an open prediction");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHelix()->endPrediction(
|
||||||
|
roomId, prediction.id, true, {},
|
||||||
|
[channel](const HelixPrediction &data) {
|
||||||
|
int totalPoints = 0;
|
||||||
|
int numUsers = 0;
|
||||||
|
for (const auto &outcome : data.outcomes)
|
||||||
|
{
|
||||||
|
totalPoints += outcome.channelPoints;
|
||||||
|
numUsers += outcome.users;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel->addSystemMessage(
|
||||||
|
QString("Refunded %1 points to %2 users for "
|
||||||
|
"prediction: '%3'")
|
||||||
|
.arg(localizeNumbers(totalPoints),
|
||||||
|
localizeNumbers(numUsers), data.title));
|
||||||
|
},
|
||||||
|
[channel](const auto &error) {
|
||||||
|
channel->addSystemMessage("Failed to cancel prediction - " +
|
||||||
|
error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[channel = ctx.channel](const auto &error) {
|
||||||
|
channel->addSystemMessage("Failed to query predictions - " + error);
|
||||||
|
});
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino::commands
|
} // namespace chatterino::commands
|
||||||
|
|||||||
@@ -13,4 +13,10 @@ namespace chatterino::commands {
|
|||||||
/// /prediction
|
/// /prediction
|
||||||
QString createPrediction(const CommandContext &ctx);
|
QString createPrediction(const CommandContext &ctx);
|
||||||
|
|
||||||
|
/// /lockprediction
|
||||||
|
QString lockPrediction(const CommandContext &ctx);
|
||||||
|
|
||||||
|
/// /cancelprediction
|
||||||
|
QString cancelPrediction(const CommandContext &ctx);
|
||||||
|
|
||||||
} // namespace chatterino::commands
|
} // namespace chatterino::commands
|
||||||
|
|||||||
@@ -3426,6 +3426,117 @@ void Helix::createPrediction(const QString broadcasterID, const QString title,
|
|||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Helix::getPredictions(const QString broadcasterID, QStringList ids,
|
||||||
|
const int first, const QString after,
|
||||||
|
ResultCallback<HelixPredictions> successCallback,
|
||||||
|
FailureCallback<QString> failureCallback)
|
||||||
|
{
|
||||||
|
QUrlQuery urlQuery;
|
||||||
|
urlQuery.addQueryItem("broadcaster_id", broadcasterID);
|
||||||
|
urlQuery.addQueryItem("first", QString::number(first));
|
||||||
|
|
||||||
|
if (!after.isEmpty())
|
||||||
|
{
|
||||||
|
urlQuery.addQueryItem("after", after);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &id : ids)
|
||||||
|
{
|
||||||
|
urlQuery.addQueryItem("id", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->makeGet("predictions", urlQuery)
|
||||||
|
.onSuccess([successCallback](const auto &result) {
|
||||||
|
if (result.status() != 200)
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoTwitch)
|
||||||
|
<< "Success result for getting predictions was "
|
||||||
|
<< result.formatError() << "but we expected it to be 200";
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto response = result.parseJson();
|
||||||
|
successCallback(HelixPredictions(response));
|
||||||
|
})
|
||||||
|
.onError([failureCallback](const auto &result) -> void {
|
||||||
|
if (!result.status())
|
||||||
|
{
|
||||||
|
failureCallback(result.formatError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto obj = result.parseJson();
|
||||||
|
auto message = obj.value("message").toString();
|
||||||
|
if (!message.isEmpty())
|
||||||
|
{
|
||||||
|
failureCallback(message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
failureCallback(result.formatError());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// End prediction can lock, cancel, or resolve an outstanding prediction.
|
||||||
|
void Helix::endPrediction(const QString broadcasterID, const QString id,
|
||||||
|
const bool refundPoints,
|
||||||
|
const QString winningOutcomeID,
|
||||||
|
ResultCallback<HelixPrediction> successCallback,
|
||||||
|
FailureCallback<QString> failureCallback)
|
||||||
|
{
|
||||||
|
QJsonObject payload;
|
||||||
|
payload.insert("broadcaster_id", broadcasterID);
|
||||||
|
payload.insert("id", id);
|
||||||
|
if (refundPoints)
|
||||||
|
{
|
||||||
|
payload.insert("status", "CANCELED");
|
||||||
|
}
|
||||||
|
else if (winningOutcomeID.isEmpty())
|
||||||
|
{
|
||||||
|
payload.insert("status", "LOCKED");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
payload.insert("status", "RESOLVED");
|
||||||
|
payload.insert("winning_outcome_id", winningOutcomeID);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->makePatch("predictions", {})
|
||||||
|
.json(payload)
|
||||||
|
.onSuccess([successCallback](const NetworkResult &result) {
|
||||||
|
if (result.status() != 200)
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoTwitch)
|
||||||
|
<< "Success result for ending a prediction was "
|
||||||
|
<< result.formatError() << "but we expected it to be 200";
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto response = result.parseJson();
|
||||||
|
const auto data = HelixPredictions(response);
|
||||||
|
successCallback(data.predictions.front());
|
||||||
|
})
|
||||||
|
.onError([failureCallback](const NetworkResult &result) -> void {
|
||||||
|
if (!result.status())
|
||||||
|
{
|
||||||
|
failureCallback(result.formatError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto obj = result.parseJson();
|
||||||
|
const auto message = obj.value("message").toString();
|
||||||
|
if (!message.isEmpty())
|
||||||
|
{
|
||||||
|
failureCallback(message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
failureCallback(result.formatError());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
void Helix::createEventSubSubscription(
|
void Helix::createEventSubSubscription(
|
||||||
const eventsub::SubscriptionRequest &request, const QString &sessionID,
|
const eventsub::SubscriptionRequest &request, const QString &sessionID,
|
||||||
ResultCallback<HelixCreateEventSubSubscriptionResponse> successCallback,
|
ResultCallback<HelixCreateEventSubSubscriptionResponse> successCallback,
|
||||||
|
|||||||
@@ -488,8 +488,9 @@ struct HelixPoll {
|
|||||||
, title(jsonObject.value("title").toString())
|
, title(jsonObject.value("title").toString())
|
||||||
, status(jsonObject.value("status").toString())
|
, status(jsonObject.value("status").toString())
|
||||||
{
|
{
|
||||||
for (const auto &data = jsonObject.value("choices").toArray();
|
const auto &data = jsonObject.value("choices").toArray();
|
||||||
const auto &c : data)
|
this->choices.reserve(data.size());
|
||||||
|
for (const auto &c : data)
|
||||||
{
|
{
|
||||||
HelixPollChoice choice(c.toObject());
|
HelixPollChoice choice(c.toObject());
|
||||||
this->choices.push_back(choice);
|
this->choices.push_back(choice);
|
||||||
@@ -504,8 +505,9 @@ struct HelixPolls {
|
|||||||
|
|
||||||
explicit HelixPolls(const QJsonObject &jsonObject)
|
explicit HelixPolls(const QJsonObject &jsonObject)
|
||||||
{
|
{
|
||||||
for (const auto &data = jsonObject.value("data").toArray();
|
const auto &data = jsonObject.value("data").toArray();
|
||||||
const auto &p : data)
|
this->polls.reserve(data.size());
|
||||||
|
for (const auto &p : data)
|
||||||
{
|
{
|
||||||
HelixPoll poll(p.toObject());
|
HelixPoll poll(p.toObject());
|
||||||
this->polls.push_back(poll);
|
this->polls.push_back(poll);
|
||||||
@@ -513,6 +515,61 @@ struct HelixPolls {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct HelixPredictionOutcome {
|
||||||
|
QString id;
|
||||||
|
QString title;
|
||||||
|
int users;
|
||||||
|
int channelPoints;
|
||||||
|
|
||||||
|
explicit HelixPredictionOutcome(const QJsonObject &jsonObject)
|
||||||
|
: id(jsonObject.value("id").toString())
|
||||||
|
, title(jsonObject.value("title").toString())
|
||||||
|
, users(jsonObject.value("users").toInt())
|
||||||
|
, channelPoints(jsonObject.value("channel_points").toInt())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HelixPrediction {
|
||||||
|
QString id;
|
||||||
|
QString title;
|
||||||
|
QString winningOutcomeID;
|
||||||
|
QString status;
|
||||||
|
std::vector<HelixPredictionOutcome> outcomes;
|
||||||
|
|
||||||
|
explicit HelixPrediction(const QJsonObject &jsonObject)
|
||||||
|
: id(jsonObject.value("id").toString())
|
||||||
|
, title(jsonObject.value("title").toString())
|
||||||
|
, winningOutcomeID(jsonObject.value("winning_outcome_id").toString())
|
||||||
|
, status(jsonObject.value("status").toString())
|
||||||
|
{
|
||||||
|
const auto &data = jsonObject.value("outcomes").toArray();
|
||||||
|
this->outcomes.reserve(data.size());
|
||||||
|
for (const auto &o : data)
|
||||||
|
{
|
||||||
|
HelixPredictionOutcome outcome(o.toObject());
|
||||||
|
this->outcomes.push_back(outcome);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HelixPredictions {
|
||||||
|
std::vector<HelixPrediction> predictions;
|
||||||
|
|
||||||
|
HelixPredictions() = default;
|
||||||
|
|
||||||
|
explicit HelixPredictions(const QJsonObject &jsonObject)
|
||||||
|
{
|
||||||
|
const auto &data = jsonObject.value("data").toArray();
|
||||||
|
this->predictions.reserve(data.size());
|
||||||
|
for (const auto &p : data)
|
||||||
|
{
|
||||||
|
HelixPrediction prediction(p.toObject());
|
||||||
|
this->predictions.push_back(prediction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
enum class HelixAnnouncementColor {
|
enum class HelixAnnouncementColor {
|
||||||
Blue,
|
Blue,
|
||||||
Green,
|
Green,
|
||||||
@@ -1286,6 +1343,18 @@ public:
|
|||||||
ResultCallback<> successCallback,
|
ResultCallback<> successCallback,
|
||||||
FailureCallback<QString> failureCallback) = 0;
|
FailureCallback<QString> failureCallback) = 0;
|
||||||
|
|
||||||
|
/// https://dev.twitch.tv/docs/api/reference#get-predictions
|
||||||
|
virtual void getPredictions(
|
||||||
|
QString broadcasterID, QStringList ids, int first, QString after,
|
||||||
|
ResultCallback<HelixPredictions> successCallback,
|
||||||
|
FailureCallback<QString> failureCallback) = 0;
|
||||||
|
|
||||||
|
/// https://dev.twitch.tv/docs/api/reference#end-prediction
|
||||||
|
virtual void endPrediction(QString broadcasterID, QString id,
|
||||||
|
bool refundPoints, QString winningOutcomeID,
|
||||||
|
ResultCallback<HelixPrediction> successCallback,
|
||||||
|
FailureCallback<QString> failureCallback) = 0;
|
||||||
|
|
||||||
// https://dev.twitch.tv/docs/api/reference/#create-eventsub-subscription
|
// https://dev.twitch.tv/docs/api/reference/#create-eventsub-subscription
|
||||||
virtual void createEventSubSubscription(
|
virtual void createEventSubSubscription(
|
||||||
const eventsub::SubscriptionRequest &request, const QString &sessionID,
|
const eventsub::SubscriptionRequest &request, const QString &sessionID,
|
||||||
@@ -1663,6 +1732,18 @@ public:
|
|||||||
ResultCallback<> successCallback,
|
ResultCallback<> successCallback,
|
||||||
FailureCallback<QString> failureCallback) final;
|
FailureCallback<QString> failureCallback) final;
|
||||||
|
|
||||||
|
/// https://dev.twitch.tv/docs/api/reference#get-predictions
|
||||||
|
void getPredictions(QString broadcasterID, QStringList ids, int first,
|
||||||
|
QString after,
|
||||||
|
ResultCallback<HelixPredictions> successCallback,
|
||||||
|
FailureCallback<QString> failureCallback) final;
|
||||||
|
|
||||||
|
/// https://dev.twitch.tv/docs/api/reference#end-prediction
|
||||||
|
void endPrediction(QString broadcasterID, QString id, bool refundPoints,
|
||||||
|
QString winningOutcomeID,
|
||||||
|
ResultCallback<HelixPrediction> successCallback,
|
||||||
|
FailureCallback<QString> failureCallback) final;
|
||||||
|
|
||||||
// https://dev.twitch.tv/docs/api/reference/#create-eventsub-subscription
|
// https://dev.twitch.tv/docs/api/reference/#create-eventsub-subscription
|
||||||
void createEventSubSubscription(
|
void createEventSubSubscription(
|
||||||
const eventsub::SubscriptionRequest &request, const QString &sessionID,
|
const eventsub::SubscriptionRequest &request, const QString &sessionID,
|
||||||
|
|||||||
Reference in New Issue
Block a user