chore: migrate /vips command to Helix call (#4053)
* feat(helix): create response model for VIP listing * feat(helix): stub out channel/vips request + handler * feat(helix): parse VIPs list from data and pass to callback * feat(helix): handle errors when getting VIP list then pass to callback * feat(command): add barebones handler for helix-based /vips * feat(command): provide better /vips output when user is not broadcaster * chore(format): bulk reformat with clang-format * chore(changelog): add entry for /vips Helix migration * fix(helix): use correct method when calling VIP list endpoint * fix(helix): use correct VIP list endpoint * chore(tidy): please clang-tidy by marking parameter as unused * feat(command): display unsorted VIP list returned from Helix API * feat(settings): clone raid timegate settings for /vips * feat(command): check /vips timegate setting before execution * feat(command): handle 0 VIPs from Helix response * feat(command): sort users alphabetically from Helix VIPs response * fix(command): highlight users in Helix /vips output to match IRC * fix(command): replace dynamic /vips error message with hardcoded string * chore(comment): remove TODO comment that was DONE * chore(format): bulk reformat using clang-format * fix(command): send 0 VIP message after creation * chore: apply suggestions from Felanbird * fix(helix): change mention of user ban to VIPs in VIP list error message * feat(helix): distinguish non-broadcaster auth error when getting VIPs * chore(command): move handling of non-broadcaster /vips usage to API response * chore(format): re-indent multiline string to get away from 80 char limit * reformat * fix tests Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
@@ -2936,6 +2937,137 @@ void CommandController::initialize(Settings &, Paths &paths)
|
||||
return runWhisperCommand(words, channel);
|
||||
});
|
||||
}
|
||||
|
||||
auto formatVIPListError = [](HelixListVIPsError error,
|
||||
const QString &message) -> QString {
|
||||
using Error = HelixListVIPsError;
|
||||
|
||||
QString errorMessage = QString("Failed to list VIPs - ");
|
||||
|
||||
switch (error)
|
||||
{
|
||||
case Error::Forwarded: {
|
||||
errorMessage += message;
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Ratelimited: {
|
||||
errorMessage += "You are being ratelimited by Twitch. Try "
|
||||
"again in a few seconds.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::UserMissingScope: {
|
||||
// TODO(pajlada): Phrase MISSING_REQUIRED_SCOPE
|
||||
errorMessage += "Missing required scope. "
|
||||
"Re-login with your "
|
||||
"account and try again.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::UserNotAuthorized: {
|
||||
// TODO(pajlada): Phrase MISSING_PERMISSION
|
||||
errorMessage += "You don't have permission to "
|
||||
"perform that action.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::UserNotBroadcaster: {
|
||||
errorMessage +=
|
||||
"Due to Twitch restrictions, "
|
||||
"this command can only be used by the broadcaster. "
|
||||
"To see the list of VIPs you must use the Twitch website.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Unknown: {
|
||||
errorMessage += "An unknown error has occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return errorMessage;
|
||||
};
|
||||
|
||||
this->registerCommand(
|
||||
"/vips",
|
||||
[formatVIPListError](const QStringList &words,
|
||||
auto channel) -> QString {
|
||||
switch (getSettings()->helixTimegateVIPs.getValue())
|
||||
{
|
||||
case HelixTimegateOverride::Timegate: {
|
||||
if (areIRCCommandsStillAvailable())
|
||||
{
|
||||
return useIRCCommand(words);
|
||||
}
|
||||
|
||||
// fall through to Helix logic
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixTimegateOverride::AlwaysUseIRC: {
|
||||
return useIRCCommand(words);
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixTimegateOverride::AlwaysUseHelix: {
|
||||
// do nothing and fall through to Helix logic
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
auto *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get());
|
||||
if (twitchChannel == nullptr)
|
||||
{
|
||||
channel->addMessage(makeSystemMessage(
|
||||
"The /vips command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
if (currentUser->isAnon())
|
||||
{
|
||||
channel->addMessage(makeSystemMessage(
|
||||
"Due to Twitch restrictions, " //
|
||||
"this command can only be used by the broadcaster. "
|
||||
"To see the list of VIPs you must use the "
|
||||
"Twitch website."));
|
||||
return "";
|
||||
}
|
||||
|
||||
getHelix()->getChannelVIPs(
|
||||
twitchChannel->roomId(),
|
||||
[channel, twitchChannel](const std::vector<HelixVip> &vipList) {
|
||||
if (vipList.empty())
|
||||
{
|
||||
channel->addMessage(makeSystemMessage(
|
||||
"This channel does not have any VIPs."));
|
||||
return;
|
||||
}
|
||||
|
||||
auto messagePrefix =
|
||||
QString("The VIPs of this channel are");
|
||||
auto entries = QStringList();
|
||||
|
||||
for (const auto &vip : vipList)
|
||||
{
|
||||
entries.append(vip.userName);
|
||||
}
|
||||
|
||||
entries.sort(Qt::CaseInsensitive);
|
||||
|
||||
MessageBuilder builder;
|
||||
TwitchMessageBuilder::listOfUsersSystemMessage(
|
||||
messagePrefix, entries, twitchChannel, &builder);
|
||||
|
||||
channel->addMessage(builder.release());
|
||||
},
|
||||
[channel, formatVIPListError](auto error, auto message) {
|
||||
auto errorMessage = formatVIPListError(error, message);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
|
||||
return "";
|
||||
});
|
||||
}
|
||||
|
||||
void CommandController::save()
|
||||
|
||||
Reference in New Issue
Block a user