feat: add broadcaster-only /completeprediction command (#6632)
Reviewed-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
+1
-1
@@ -18,7 +18,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`, `/cancelprediction`, and `/lockprediction` commands. (#6583, #6612)
|
- Minor: Added broadcaster-only `/prediction`, `/cancelprediction`, `/lockprediction`, and `/completeprediction` commands. (#6583, #6612, #6632)
|
||||||
- Minor: Added support for BetterTTV Pro subscriber badges. (#6625)
|
- Minor: Added support for BetterTTV Pro subscriber badges. (#6625)
|
||||||
- Bugfix: Moderation checks now include the lead moderator badge. (#6642)
|
- Bugfix: Moderation checks now include the lead moderator badge. (#6642)
|
||||||
- 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)
|
||||||
|
|||||||
@@ -490,6 +490,7 @@ CommandController::CommandController(const Paths &paths)
|
|||||||
this->registerCommand("/prediction", &commands::createPrediction);
|
this->registerCommand("/prediction", &commands::createPrediction);
|
||||||
this->registerCommand("/lockprediction", &commands::lockPrediction);
|
this->registerCommand("/lockprediction", &commands::lockPrediction);
|
||||||
this->registerCommand("/cancelprediction", &commands::cancelPrediction);
|
this->registerCommand("/cancelprediction", &commands::cancelPrediction);
|
||||||
|
this->registerCommand("/completeprediction", &commands::completePrediction);
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "util/Helpers.hpp"
|
#include "util/Helpers.hpp"
|
||||||
|
|
||||||
|
#include <QCommandLineParser>
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -218,4 +221,179 @@ QString cancelPrediction(const CommandContext &ctx)
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString completePrediction(const CommandContext &ctx)
|
||||||
|
{
|
||||||
|
const auto usage = QStringLiteral(
|
||||||
|
R"(Usage: /completeprediction --choice "<choice>" or /completeprediction --index <index> - Selects a winner for an outstanding prediction. The choice title must exactly match the wording in the prediction. Alternatively, you may specify the one-based index of the winning outcome.)");
|
||||||
|
|
||||||
|
if (ctx.twitchChannel == nullptr)
|
||||||
|
{
|
||||||
|
const auto err = QStringLiteral(
|
||||||
|
"The /completeprediction command only works in Twitch channels");
|
||||||
|
if (ctx.channel != nullptr)
|
||||||
|
{
|
||||||
|
ctx.channel->addSystemMessage(err);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoCommands) << "Invalid command context:" << err;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define arguments
|
||||||
|
QCommandLineParser parser;
|
||||||
|
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
|
||||||
|
parser.setOptionsAfterPositionalArgumentsMode(
|
||||||
|
QCommandLineParser::ParseAsOptions);
|
||||||
|
QCommandLineOption choiceOption(
|
||||||
|
{"c", "choice"}, "The prediction outcome to select as the winner",
|
||||||
|
"choice");
|
||||||
|
QCommandLineOption indexOption(
|
||||||
|
{"i", "index"},
|
||||||
|
"The one-based index of the prediction outcome to select as the winner",
|
||||||
|
"index");
|
||||||
|
parser.addOptions({
|
||||||
|
choiceOption,
|
||||||
|
indexOption,
|
||||||
|
});
|
||||||
|
const auto joined = ctx.words.join(" ");
|
||||||
|
parser.parse(QProcess::splitCommand(joined));
|
||||||
|
|
||||||
|
// Input validation
|
||||||
|
const bool hasName = parser.isSet(choiceOption);
|
||||||
|
const bool hasIndex = parser.isSet(indexOption);
|
||||||
|
if (hasName && hasIndex)
|
||||||
|
{
|
||||||
|
ctx.channel->addSystemMessage(
|
||||||
|
"You may not specify choice and index simultaneously - " + usage);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (!hasIndex && !hasName)
|
||||||
|
{
|
||||||
|
ctx.channel->addSystemMessage(
|
||||||
|
"You must specify either choice or index - " + usage);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
int targetIndex = 0;
|
||||||
|
QString targetName;
|
||||||
|
if (hasName)
|
||||||
|
{
|
||||||
|
targetName = parser.value(choiceOption);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool ok = true;
|
||||||
|
targetIndex = parser.value(indexOption).toInt(&ok);
|
||||||
|
if (!ok || targetIndex <= 0)
|
||||||
|
{
|
||||||
|
ctx.channel->addSystemMessage("Invalid index - " + usage);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform action
|
||||||
|
const auto roomId = ctx.twitchChannel->roomId();
|
||||||
|
getHelix()->getPredictions(
|
||||||
|
roomId, {}, 1, {},
|
||||||
|
[channel = ctx.channel, roomId, hasIndex, targetIndex,
|
||||||
|
targetName](const auto &queryResult) {
|
||||||
|
if (queryResult.predictions.empty())
|
||||||
|
{
|
||||||
|
channel->addSystemMessage(
|
||||||
|
"You must start a prediction before you can complete one");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto prediction = queryResult.predictions.front();
|
||||||
|
if (prediction.status != "ACTIVE" && prediction.status != "LOCKED")
|
||||||
|
{
|
||||||
|
channel->addSystemMessage(
|
||||||
|
"Could not find an open prediction to complete");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// identify winning outcome
|
||||||
|
auto outcomes = prediction.outcomes;
|
||||||
|
QString winnerId = "";
|
||||||
|
if (hasIndex)
|
||||||
|
{
|
||||||
|
auto maxIndex = outcomes.size();
|
||||||
|
if (targetIndex > maxIndex)
|
||||||
|
{
|
||||||
|
channel->addSystemMessage(
|
||||||
|
QString("Specified index (%1) exceeds the number of "
|
||||||
|
"outcomes (%2)")
|
||||||
|
.arg(QString::number(targetIndex))
|
||||||
|
.arg(QString::number(maxIndex)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
winnerId = outcomes[targetIndex - 1].id;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (const auto &outcome : outcomes)
|
||||||
|
{
|
||||||
|
if (outcome.title == targetName)
|
||||||
|
{
|
||||||
|
winnerId = outcome.id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (winnerId == "")
|
||||||
|
{
|
||||||
|
auto options = std::accumulate(
|
||||||
|
outcomes.begin(), outcomes.end(), QString{},
|
||||||
|
[](const QString &acc,
|
||||||
|
const HelixPredictionOutcome &outcome) {
|
||||||
|
auto title = "'" + outcome.title + "'";
|
||||||
|
return acc.isEmpty() ? title : acc + ", " + title;
|
||||||
|
});
|
||||||
|
channel->addSystemMessage(
|
||||||
|
"Could not find the desired winner. Options include: " +
|
||||||
|
options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve prediction
|
||||||
|
getHelix()->endPrediction(
|
||||||
|
roomId, prediction.id, false, winnerId,
|
||||||
|
[channel](const HelixPrediction &result) {
|
||||||
|
int totalPoints = 0;
|
||||||
|
HelixPredictionOutcome winner = result.outcomes.front();
|
||||||
|
for (const auto &outcome : result.outcomes)
|
||||||
|
{
|
||||||
|
totalPoints += outcome.channelPoints;
|
||||||
|
if (outcome.id == result.winningOutcomeID)
|
||||||
|
{
|
||||||
|
winner = outcome;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
channel->addSystemMessage(
|
||||||
|
QString("Completed prediction: %1 - '%2' won %3 points "
|
||||||
|
"(%4 profit) to be distributed among %5 users")
|
||||||
|
.arg(result.title, winner.title,
|
||||||
|
localizeNumbers(totalPoints),
|
||||||
|
localizeNumbers(totalPoints -
|
||||||
|
winner.channelPoints),
|
||||||
|
localizeNumbers(winner.users)));
|
||||||
|
},
|
||||||
|
[channel](const auto &error) {
|
||||||
|
channel->addSystemMessage(
|
||||||
|
"Failed to complete prediction - " + error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[channel = ctx.channel](const auto &error) {
|
||||||
|
channel->addSystemMessage(
|
||||||
|
"Failed to query predictions to complete - " + error);
|
||||||
|
});
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino::commands
|
} // namespace chatterino::commands
|
||||||
|
|||||||
@@ -19,4 +19,7 @@ QString lockPrediction(const CommandContext &ctx);
|
|||||||
/// /cancelprediction
|
/// /cancelprediction
|
||||||
QString cancelPrediction(const CommandContext &ctx);
|
QString cancelPrediction(const CommandContext &ctx);
|
||||||
|
|
||||||
|
/// /completeprediction
|
||||||
|
QString completePrediction(const CommandContext &ctx);
|
||||||
|
|
||||||
} // namespace chatterino::commands
|
} // namespace chatterino::commands
|
||||||
|
|||||||
Reference in New Issue
Block a user