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: Add categories to the emoji viewer. (#6598)
|
||||
- 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)
|
||||
- Bugfix: Moderation checks now include the lead moderator badge. (#6642)
|
||||
- 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("/lockprediction", &commands::lockPrediction);
|
||||
this->registerCommand("/cancelprediction", &commands::cancelPrediction);
|
||||
this->registerCommand("/completeprediction", &commands::completePrediction);
|
||||
|
||||
this->registerCommand("/c2-set-logging-rules", &commands::setLoggingRules);
|
||||
this->registerCommand("/c2-theme-autoreload", &commands::toggleThemeReload);
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QProcess>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace {
|
||||
@@ -218,4 +221,179 @@ QString cancelPrediction(const CommandContext &ctx)
|
||||
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
|
||||
|
||||
@@ -19,4 +19,7 @@ QString lockPrediction(const CommandContext &ctx);
|
||||
/// /cancelprediction
|
||||
QString cancelPrediction(const CommandContext &ctx);
|
||||
|
||||
/// /completeprediction
|
||||
QString completePrediction(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
||||
|
||||
Reference in New Issue
Block a user