mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
refactor: Move all commands to their own files (#4946)
This commit is contained in:
parent
d40b0a6c1d
commit
f89642ec66
41 changed files with 3405 additions and 2605 deletions
|
@ -59,14 +59,50 @@ set(SOURCE_FILES
|
|||
|
||||
controllers/commands/builtin/chatterino/Debugging.cpp
|
||||
controllers/commands/builtin/chatterino/Debugging.hpp
|
||||
controllers/commands/builtin/Misc.cpp
|
||||
controllers/commands/builtin/Misc.hpp
|
||||
controllers/commands/builtin/twitch/AddModerator.cpp
|
||||
controllers/commands/builtin/twitch/AddModerator.hpp
|
||||
controllers/commands/builtin/twitch/AddVIP.cpp
|
||||
controllers/commands/builtin/twitch/AddVIP.hpp
|
||||
controllers/commands/builtin/twitch/Announce.cpp
|
||||
controllers/commands/builtin/twitch/Announce.hpp
|
||||
controllers/commands/builtin/twitch/Ban.cpp
|
||||
controllers/commands/builtin/twitch/Ban.hpp
|
||||
controllers/commands/builtin/twitch/Block.cpp
|
||||
controllers/commands/builtin/twitch/Block.hpp
|
||||
controllers/commands/builtin/twitch/ChatSettings.cpp
|
||||
controllers/commands/builtin/twitch/ChatSettings.hpp
|
||||
controllers/commands/builtin/twitch/Chatters.cpp
|
||||
controllers/commands/builtin/twitch/Chatters.hpp
|
||||
controllers/commands/builtin/twitch/DeleteMessages.cpp
|
||||
controllers/commands/builtin/twitch/DeleteMessages.hpp
|
||||
controllers/commands/builtin/twitch/GetModerators.cpp
|
||||
controllers/commands/builtin/twitch/GetModerators.hpp
|
||||
controllers/commands/builtin/twitch/GetVIPs.cpp
|
||||
controllers/commands/builtin/twitch/GetVIPs.hpp
|
||||
controllers/commands/builtin/twitch/Raid.cpp
|
||||
controllers/commands/builtin/twitch/Raid.hpp
|
||||
controllers/commands/builtin/twitch/RemoveModerator.cpp
|
||||
controllers/commands/builtin/twitch/RemoveModerator.hpp
|
||||
controllers/commands/builtin/twitch/RemoveVIP.cpp
|
||||
controllers/commands/builtin/twitch/RemoveVIP.hpp
|
||||
controllers/commands/builtin/twitch/SendReply.cpp
|
||||
controllers/commands/builtin/twitch/SendReply.hpp
|
||||
controllers/commands/builtin/twitch/SendWhisper.cpp
|
||||
controllers/commands/builtin/twitch/SendWhisper.hpp
|
||||
controllers/commands/builtin/twitch/ShieldMode.cpp
|
||||
controllers/commands/builtin/twitch/ShieldMode.hpp
|
||||
controllers/commands/builtin/twitch/Shoutout.cpp
|
||||
controllers/commands/builtin/twitch/Shoutout.hpp
|
||||
controllers/commands/builtin/twitch/Ban.cpp
|
||||
controllers/commands/builtin/twitch/Ban.hpp
|
||||
controllers/commands/builtin/twitch/StartCommercial.cpp
|
||||
controllers/commands/builtin/twitch/StartCommercial.hpp
|
||||
controllers/commands/builtin/twitch/Unban.cpp
|
||||
controllers/commands/builtin/twitch/Unban.hpp
|
||||
controllers/commands/builtin/twitch/UpdateChannel.cpp
|
||||
controllers/commands/builtin/twitch/UpdateChannel.hpp
|
||||
controllers/commands/builtin/twitch/UpdateColor.cpp
|
||||
controllers/commands/builtin/twitch/UpdateColor.hpp
|
||||
controllers/commands/CommandContext.hpp
|
||||
controllers/commands/CommandController.cpp
|
||||
controllers/commands/CommandController.hpp
|
||||
|
|
File diff suppressed because it is too large
Load diff
629
src/controllers/commands/builtin/Misc.cpp
Normal file
629
src/controllers/commands/builtin/Misc.cpp
Normal file
|
@ -0,0 +1,629 @@
|
|||
#include "controllers/commands/builtin/Misc.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "controllers/userdata/UserDataController.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/Clipboard.hpp"
|
||||
#include "util/FormatTime.hpp"
|
||||
#include "util/IncognitoBrowser.hpp"
|
||||
#include "util/StreamLink.hpp"
|
||||
#include "util/Twitch.hpp"
|
||||
#include "widgets/dialogs/UserInfoPopup.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
#include "widgets/splits/Split.hpp"
|
||||
#include "widgets/splits/SplitContainer.hpp"
|
||||
#include "widgets/Window.hpp"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString follow(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"Twitch has removed the ability to follow users through "
|
||||
"third-party applications. For more information, see "
|
||||
"https://github.com/Chatterino/chatterino2/issues/3076"));
|
||||
return "";
|
||||
}
|
||||
|
||||
QString unfollow(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"Twitch has removed the ability to unfollow users through "
|
||||
"third-party applications. For more information, see "
|
||||
"https://github.com/Chatterino/chatterino2/issues/3076"));
|
||||
return "";
|
||||
}
|
||||
|
||||
QString uptime(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /uptime command only works in Twitch Channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
const auto &streamStatus = ctx.twitchChannel->accessStreamStatus();
|
||||
|
||||
QString messageText =
|
||||
streamStatus->live ? streamStatus->uptime : "Channel is not live.";
|
||||
|
||||
ctx.channel->addMessage(makeSystemMessage(messageText));
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString user(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("Usage: /user <user> [channel]"));
|
||||
return "";
|
||||
}
|
||||
QString userName = ctx.words[1];
|
||||
stripUserName(userName);
|
||||
|
||||
QString channelName = ctx.channel->getName();
|
||||
|
||||
if (ctx.words.size() > 2)
|
||||
{
|
||||
channelName = ctx.words[2];
|
||||
stripChannelName(channelName);
|
||||
}
|
||||
openTwitchUsercard(channelName, userName);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString requests(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
QString target(ctx.words.value(1));
|
||||
|
||||
if (target.isEmpty())
|
||||
{
|
||||
if (ctx.channel->getType() == Channel::Type::Twitch &&
|
||||
!ctx.channel->isEmpty())
|
||||
{
|
||||
target = ctx.channel->getName();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"Usage: /requests [channel]. You can also use the command "
|
||||
"without arguments in any Twitch channel to open its "
|
||||
"channel points requests queue. Only the broadcaster and "
|
||||
"moderators have permission to view the queue."));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
stripChannelName(target);
|
||||
QDesktopServices::openUrl(QUrl(
|
||||
QString("https://www.twitch.tv/popout/%1/reward-queue").arg(target)));
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString lowtrust(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
QString target(ctx.words.value(1));
|
||||
|
||||
if (target.isEmpty())
|
||||
{
|
||||
if (ctx.channel->getType() == Channel::Type::Twitch &&
|
||||
!ctx.channel->isEmpty())
|
||||
{
|
||||
target = ctx.channel->getName();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"Usage: /lowtrust [channel]. You can also use the command "
|
||||
"without arguments in any Twitch channel to open its "
|
||||
"suspicious user activity feed. Only the broadcaster and "
|
||||
"moderators have permission to view this feed."));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
stripChannelName(target);
|
||||
QDesktopServices::openUrl(QUrl(
|
||||
QString("https://www.twitch.tv/popout/moderator/%1/low-trust-users")
|
||||
.arg(target)));
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString clip(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (const auto type = ctx.channel->getType();
|
||||
type != Channel::Type::Twitch && type != Channel::Type::TwitchWatching)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /clip command only works in Twitch Channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /clip command only works in Twitch Channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
ctx.twitchChannel->createClip();
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString marker(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /marker command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
// Avoid Helix calls without Client ID and/or OAuth Token
|
||||
if (getApp()->accounts->twitch.getCurrent()->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"You need to be logged in to create stream markers!"));
|
||||
return "";
|
||||
}
|
||||
|
||||
// Exact same message as in webchat
|
||||
if (!ctx.twitchChannel->isLive())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"You can only add stream markers during live streams. Try "
|
||||
"again when the channel is live streaming."));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto arguments = ctx.words;
|
||||
arguments.removeFirst();
|
||||
|
||||
getHelix()->createStreamMarker(
|
||||
// Limit for description is 140 characters, webchat just crops description
|
||||
// if it's >140 characters, so we're doing the same thing
|
||||
ctx.twitchChannel->roomId(), arguments.join(" ").left(140),
|
||||
[channel{ctx.channel},
|
||||
arguments](const HelixStreamMarker &streamMarker) {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("Successfully added a stream marker at %1%2")
|
||||
.arg(formatTime(streamMarker.positionSeconds))
|
||||
.arg(streamMarker.description.isEmpty()
|
||||
? ""
|
||||
: QString(": \"%1\"")
|
||||
.arg(streamMarker.description))));
|
||||
},
|
||||
[channel{ctx.channel}](auto error) {
|
||||
QString errorMessage("Failed to create stream marker - ");
|
||||
|
||||
switch (error)
|
||||
{
|
||||
case HelixStreamMarkerError::UserNotAuthorized: {
|
||||
errorMessage +=
|
||||
"you don't have permission to perform that action.";
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixStreamMarkerError::UserNotAuthenticated: {
|
||||
errorMessage += "you need to re-authenticate.";
|
||||
}
|
||||
break;
|
||||
|
||||
// This would most likely happen if the service is down, or if the JSON payload returned has changed format
|
||||
case HelixStreamMarkerError::Unknown:
|
||||
default: {
|
||||
errorMessage += "an unknown error occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString streamlink(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
QString target(ctx.words.value(1));
|
||||
|
||||
if (target.isEmpty())
|
||||
{
|
||||
if (ctx.channel->getType() == Channel::Type::Twitch &&
|
||||
!ctx.channel->isEmpty())
|
||||
{
|
||||
target = ctx.channel->getName();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"/streamlink [channel]. Open specified Twitch channel in "
|
||||
"streamlink. If no channel argument is specified, open the "
|
||||
"current Twitch channel instead."));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
stripChannelName(target);
|
||||
openStreamlinkForChannel(target);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString popout(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
QString target(ctx.words.value(1));
|
||||
|
||||
if (target.isEmpty())
|
||||
{
|
||||
if (ctx.channel->getType() == Channel::Type::Twitch &&
|
||||
!ctx.channel->isEmpty())
|
||||
{
|
||||
target = ctx.channel->getName();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"Usage: /popout <channel>. You can also use the command "
|
||||
"without arguments in any Twitch channel to open its "
|
||||
"popout chat."));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
stripChannelName(target);
|
||||
QDesktopServices::openUrl(QUrl(
|
||||
QString("https://www.twitch.tv/popout/%1/chat?popout=").arg(target)));
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString popup(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
static const auto *usageMessage =
|
||||
"Usage: /popup [channel]. Open specified Twitch channel in "
|
||||
"a new window. If no channel argument is specified, open "
|
||||
"the currently selected split instead.";
|
||||
|
||||
QString target(ctx.words.value(1));
|
||||
stripChannelName(target);
|
||||
|
||||
// Popup the current split
|
||||
if (target.isEmpty())
|
||||
{
|
||||
auto *currentPage = dynamic_cast<SplitContainer *>(
|
||||
getApp()->windows->getMainWindow().getNotebook().getSelectedPage());
|
||||
if (currentPage != nullptr)
|
||||
{
|
||||
auto *currentSplit = currentPage->getSelectedSplit();
|
||||
if (currentSplit != nullptr)
|
||||
{
|
||||
currentSplit->popup();
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
ctx.channel->addMessage(makeSystemMessage(usageMessage));
|
||||
return "";
|
||||
}
|
||||
|
||||
// Open channel passed as argument in a popup
|
||||
auto *app = getApp();
|
||||
auto targetChannel = app->twitch->getOrAddChannel(target);
|
||||
app->windows->openInPopup(targetChannel);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString clearmessages(const CommandContext &ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
|
||||
auto *currentPage = dynamic_cast<SplitContainer *>(
|
||||
getApp()->windows->getMainWindow().getNotebook().getSelectedPage());
|
||||
|
||||
if (auto *split = currentPage->getSelectedSplit())
|
||||
{
|
||||
split->getChannelView().clearMessages();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString openURL(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage("Usage: /openurl <URL>"));
|
||||
return "";
|
||||
}
|
||||
|
||||
QUrl url = QUrl::fromUserInput(ctx.words.mid(1).join(" "));
|
||||
if (!url.isValid())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage("Invalid URL specified."));
|
||||
return "";
|
||||
}
|
||||
|
||||
bool res = false;
|
||||
if (supportsIncognitoLinks() && getSettings()->openLinksIncognito)
|
||||
{
|
||||
res = openLinkIncognito(url.toString(QUrl::FullyEncoded));
|
||||
}
|
||||
else
|
||||
{
|
||||
res = QDesktopServices::openUrl(url);
|
||||
}
|
||||
|
||||
if (!res)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage("Could not open URL."));
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString sendRawMessage(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.channel->isTwitchChannel())
|
||||
{
|
||||
getApp()->twitch->sendRawMessage(ctx.words.mid(1).join(" "));
|
||||
}
|
||||
else
|
||||
{
|
||||
// other code down the road handles this for IRC
|
||||
return ctx.words.join(" ");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
QString injectFakeMessage(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!ctx.channel->isTwitchChannel())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /fakemsg command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"Usage: /fakemsg (raw irc text) - injects raw irc text as "
|
||||
"if it was a message received from TMI"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto ircText = ctx.words.mid(1).join(" ");
|
||||
getApp()->twitch->addFakeMessage(ircText);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString copyToClipboard(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("Usage: /copy <text> - copies provided "
|
||||
"text to clipboard."));
|
||||
return "";
|
||||
}
|
||||
|
||||
crossPlatformCopy(ctx.words.mid(1).join(" "));
|
||||
return "";
|
||||
}
|
||||
|
||||
QString unstableSetUserClientSideColor(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("The /unstable-set-user-color command only "
|
||||
"works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
QString("Usage: %1 <TwitchUserID> [color]").arg(ctx.words.at(0))));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto userID = ctx.words.at(1);
|
||||
|
||||
auto color = ctx.words.value(2);
|
||||
|
||||
getIApp()->getUserData()->setUserColor(userID, color);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString openUsercard(const CommandContext &ctx)
|
||||
{
|
||||
auto channel = ctx.channel;
|
||||
|
||||
if (channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
channel->addMessage(
|
||||
makeSystemMessage("Usage: /usercard <username> [channel] or "
|
||||
"/usercard id:<id> [channel]"));
|
||||
return "";
|
||||
}
|
||||
|
||||
QString userName = ctx.words[1];
|
||||
stripUserName(userName);
|
||||
|
||||
if (ctx.words.size() > 2)
|
||||
{
|
||||
QString channelName = ctx.words[2];
|
||||
stripChannelName(channelName);
|
||||
|
||||
ChannelPtr channelTemp =
|
||||
getApp()->twitch->getChannelOrEmpty(channelName);
|
||||
|
||||
if (channelTemp->isEmpty())
|
||||
{
|
||||
channel->addMessage(makeSystemMessage(
|
||||
"A usercard can only be displayed for a channel that is "
|
||||
"currently opened in Chatterino."));
|
||||
return "";
|
||||
}
|
||||
|
||||
channel = channelTemp;
|
||||
}
|
||||
|
||||
// try to link to current split if possible
|
||||
Split *currentSplit = nullptr;
|
||||
auto *currentPage = dynamic_cast<SplitContainer *>(
|
||||
getApp()->windows->getMainWindow().getNotebook().getSelectedPage());
|
||||
if (currentPage != nullptr)
|
||||
{
|
||||
currentSplit = currentPage->getSelectedSplit();
|
||||
}
|
||||
|
||||
auto differentChannel =
|
||||
currentSplit != nullptr && currentSplit->getChannel() != channel;
|
||||
if (differentChannel || currentSplit == nullptr)
|
||||
{
|
||||
// not possible to use current split, try searching for one
|
||||
const auto ¬ebook = getApp()->windows->getMainWindow().getNotebook();
|
||||
auto count = notebook.getPageCount();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
auto *page = notebook.getPageAt(i);
|
||||
auto *container = dynamic_cast<SplitContainer *>(page);
|
||||
assert(container != nullptr);
|
||||
for (auto *split : container->getSplits())
|
||||
{
|
||||
if (split->getChannel() == channel)
|
||||
{
|
||||
currentSplit = split;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This would have crashed either way.
|
||||
assert(currentSplit != nullptr &&
|
||||
"something went HORRIBLY wrong with the /usercard "
|
||||
"command. It couldn't find a split for a channel which "
|
||||
"should be open.");
|
||||
}
|
||||
|
||||
auto *userPopup = new UserInfoPopup(
|
||||
getSettings()->autoCloseUserPopup,
|
||||
static_cast<QWidget *>(&(getApp()->windows->getMainWindow())),
|
||||
currentSplit);
|
||||
userPopup->setData(userName, channel);
|
||||
userPopup->moveTo(QCursor::pos(), widgets::BoundsChecking::CursorPosition);
|
||||
userPopup->show();
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
32
src/controllers/commands/builtin/Misc.hpp
Normal file
32
src/controllers/commands/builtin/Misc.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString follow(const CommandContext &ctx);
|
||||
QString unfollow(const CommandContext &ctx);
|
||||
QString uptime(const CommandContext &ctx);
|
||||
QString user(const CommandContext &ctx);
|
||||
QString requests(const CommandContext &ctx);
|
||||
QString lowtrust(const CommandContext &ctx);
|
||||
QString clip(const CommandContext &ctx);
|
||||
QString marker(const CommandContext &ctx);
|
||||
QString streamlink(const CommandContext &ctx);
|
||||
QString popout(const CommandContext &ctx);
|
||||
QString popup(const CommandContext &ctx);
|
||||
QString clearmessages(const CommandContext &ctx);
|
||||
QString openURL(const CommandContext &ctx);
|
||||
QString sendRawMessage(const CommandContext &ctx);
|
||||
QString injectFakeMessage(const CommandContext &ctx);
|
||||
QString copyToClipboard(const CommandContext &ctx);
|
||||
QString unstableSetUserClientSideColor(const CommandContext &ctx);
|
||||
QString openUsercard(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
|
@ -1,11 +1,16 @@
|
|||
#include "controllers/commands/builtin/chatterino/Debugging.hpp"
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/Env.hpp"
|
||||
#include "common/Literals.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/Image.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QLoggingCategory>
|
||||
#include <QString>
|
||||
|
||||
|
@ -63,4 +68,70 @@ QString toggleThemeReload(const CommandContext &ctx)
|
|||
return {};
|
||||
}
|
||||
|
||||
QString listEnvironmentVariables(const CommandContext &ctx)
|
||||
{
|
||||
const auto &channel = ctx.channel;
|
||||
if (channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
auto env = Env::get();
|
||||
|
||||
QStringList debugMessages{
|
||||
"recentMessagesApiUrl: " + env.recentMessagesApiUrl,
|
||||
"linkResolverUrl: " + env.linkResolverUrl,
|
||||
"twitchServerHost: " + env.twitchServerHost,
|
||||
"twitchServerPort: " + QString::number(env.twitchServerPort),
|
||||
"twitchServerSecure: " + QString::number(env.twitchServerSecure),
|
||||
};
|
||||
|
||||
for (QString &str : debugMessages)
|
||||
{
|
||||
MessageBuilder builder;
|
||||
builder.emplace<TimestampElement>(QTime::currentTime());
|
||||
builder.emplace<TextElement>(str, MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
channel->addMessage(builder.release());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
QString listArgs(const CommandContext &ctx)
|
||||
{
|
||||
const auto &channel = ctx.channel;
|
||||
if (channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
QString msg = QApplication::instance()->arguments().join(' ');
|
||||
|
||||
channel->addMessage(makeSystemMessage(msg));
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString forceImageGarbageCollection(const CommandContext &ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
|
||||
runInGuiThread([] {
|
||||
auto &iep = ImageExpirationPool::instance();
|
||||
iep.freeOld();
|
||||
});
|
||||
return "";
|
||||
}
|
||||
|
||||
QString forceImageUnload(const CommandContext &ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
|
||||
runInGuiThread([] {
|
||||
auto &iep = ImageExpirationPool::instance();
|
||||
iep.freeAll();
|
||||
});
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
||||
|
|
|
@ -14,4 +14,12 @@ QString setLoggingRules(const CommandContext &ctx);
|
|||
|
||||
QString toggleThemeReload(const CommandContext &ctx);
|
||||
|
||||
QString listEnvironmentVariables(const CommandContext &ctx);
|
||||
|
||||
QString listArgs(const CommandContext &ctx);
|
||||
|
||||
QString forceImageGarbageCollection(const CommandContext &ctx);
|
||||
|
||||
QString forceImageUnload(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
||||
|
|
131
src/controllers/commands/builtin/twitch/AddModerator.cpp
Normal file
131
src/controllers/commands/builtin/twitch/AddModerator.cpp
Normal file
|
@ -0,0 +1,131 @@
|
|||
#include "controllers/commands/builtin/twitch/AddModerator.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
#include "util/Twitch.hpp"
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString addModerator(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /mod command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"Usage: \"/mod <username>\" - Grant moderator status to a "
|
||||
"user. Use \"/mods\" to list the moderators of this channel."));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
if (currentUser->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("You must be logged in to mod someone!"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto target = ctx.words.at(1);
|
||||
stripChannelName(target);
|
||||
|
||||
getHelix()->getUserByName(
|
||||
target,
|
||||
[twitchChannel{ctx.twitchChannel},
|
||||
channel{ctx.channel}](const HelixUser &targetUser) {
|
||||
getHelix()->addChannelModerator(
|
||||
twitchChannel->roomId(), targetUser.id,
|
||||
[channel, targetUser] {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("You have added %1 as a moderator of this "
|
||||
"channel.")
|
||||
.arg(targetUser.displayName)));
|
||||
},
|
||||
[channel, targetUser](auto error, auto message) {
|
||||
QString errorMessage =
|
||||
QString("Failed to add channel moderator - ");
|
||||
|
||||
using Error = HelixAddChannelModeratorError;
|
||||
|
||||
switch (error)
|
||||
{
|
||||
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::Ratelimited: {
|
||||
errorMessage +=
|
||||
"You are being ratelimited by Twitch. Try "
|
||||
"again in a few seconds.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::TargetIsVIP: {
|
||||
errorMessage +=
|
||||
QString("%1 is currently a VIP, \"/unvip\" "
|
||||
"them and "
|
||||
"retry this command.")
|
||||
.arg(targetUser.displayName);
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::TargetAlreadyModded: {
|
||||
// Equivalent irc error
|
||||
errorMessage =
|
||||
QString("%1 is already a moderator of this "
|
||||
"channel.")
|
||||
.arg(targetUser.displayName);
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Forwarded: {
|
||||
errorMessage += message;
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Unknown:
|
||||
default: {
|
||||
errorMessage += "An unknown error has occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
},
|
||||
[channel{ctx.channel}, target] {
|
||||
// Equivalent error from IRC
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("Invalid username: %1").arg(target)));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
16
src/controllers/commands/builtin/twitch/AddModerator.hpp
Normal file
16
src/controllers/commands/builtin/twitch/AddModerator.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
/// /mod
|
||||
QString addModerator(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
112
src/controllers/commands/builtin/twitch/AddVIP.cpp
Normal file
112
src/controllers/commands/builtin/twitch/AddVIP.cpp
Normal file
|
@ -0,0 +1,112 @@
|
|||
#include "controllers/commands/builtin/twitch/AddVIP.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
#include "util/Twitch.hpp"
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString addVIP(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /vip command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"Usage: \"/vip <username>\" - Grant VIP status to a user. Use "
|
||||
"\"/vips\" to list the VIPs of this channel."));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
if (currentUser->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("You must be logged in to VIP someone!"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto target = ctx.words.at(1);
|
||||
stripChannelName(target);
|
||||
|
||||
getHelix()->getUserByName(
|
||||
target,
|
||||
[twitchChannel{ctx.twitchChannel},
|
||||
channel{ctx.channel}](const HelixUser &targetUser) {
|
||||
getHelix()->addChannelVIP(
|
||||
twitchChannel->roomId(), targetUser.id,
|
||||
[channel, targetUser] {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("You have added %1 as a VIP of this channel.")
|
||||
.arg(targetUser.displayName)));
|
||||
},
|
||||
[channel, targetUser](auto error, auto message) {
|
||||
QString errorMessage = QString("Failed to add VIP - ");
|
||||
|
||||
using Error = HelixAddChannelVIPError;
|
||||
|
||||
switch (error)
|
||||
{
|
||||
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::Ratelimited: {
|
||||
errorMessage +=
|
||||
"You are being ratelimited by Twitch. Try "
|
||||
"again in a few seconds.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Forwarded: {
|
||||
// These are actually the IRC equivalents, so we can ditch the prefix
|
||||
errorMessage = message;
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Unknown:
|
||||
default: {
|
||||
errorMessage += "An unknown error has occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
},
|
||||
[channel{ctx.channel}, target] {
|
||||
// Equivalent error from IRC
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("Invalid username: %1").arg(target)));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
16
src/controllers/commands/builtin/twitch/AddVIP.hpp
Normal file
16
src/controllers/commands/builtin/twitch/AddVIP.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
/// /vip
|
||||
QString addVIP(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
81
src/controllers/commands/builtin/twitch/Announce.cpp
Normal file
81
src/controllers/commands/builtin/twitch/Announce.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
#include "controllers/commands/builtin/twitch/Announce.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString sendAnnouncement(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"This command can only be used in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"Usage: /announce <message> - Call attention to your "
|
||||
"message with a highlight."));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto user = getApp()->accounts->twitch.getCurrent();
|
||||
if (user->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"You must be logged in to use the /announce command"));
|
||||
return "";
|
||||
}
|
||||
|
||||
getHelix()->sendChatAnnouncement(
|
||||
ctx.twitchChannel->roomId(), user->getUserId(),
|
||||
ctx.words.mid(1).join(" "), HelixAnnouncementColor::Primary,
|
||||
[]() {
|
||||
// do nothing.
|
||||
},
|
||||
[channel{ctx.channel}](auto error, auto message) {
|
||||
using Error = HelixSendChatAnnouncementError;
|
||||
QString errorMessage = QString("Failed to send announcement - ");
|
||||
|
||||
switch (error)
|
||||
{
|
||||
case Error::UserMissingScope: {
|
||||
// TODO(pajlada): Phrase MISSING_REQUIRED_SCOPE
|
||||
errorMessage +=
|
||||
"Missing required scope. Re-login with your "
|
||||
"account and try again.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Forwarded: {
|
||||
errorMessage += message;
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Unknown:
|
||||
default: {
|
||||
errorMessage += "An unknown error has occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
16
src/controllers/commands/builtin/twitch/Announce.hpp
Normal file
16
src/controllers/commands/builtin/twitch/Announce.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
/// /announce
|
||||
QString sendAnnouncement(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
166
src/controllers/commands/builtin/twitch/Block.cpp
Normal file
166
src/controllers/commands/builtin/twitch/Block.cpp
Normal file
|
@ -0,0 +1,166 @@
|
|||
#include "controllers/commands/builtin/twitch/Block.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "util/Twitch.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace chatterino;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString blockUser(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /block command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage("Usage: /block <user>"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
if (currentUser->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("You must be logged in to block someone!"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto target = ctx.words.at(1);
|
||||
stripChannelName(target);
|
||||
|
||||
getHelix()->getUserByName(
|
||||
target,
|
||||
[currentUser, channel{ctx.channel},
|
||||
target](const HelixUser &targetUser) {
|
||||
getApp()->accounts->twitch.getCurrent()->blockUser(
|
||||
targetUser.id, nullptr,
|
||||
[channel, target, targetUser] {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("You successfully blocked user %1")
|
||||
.arg(target)));
|
||||
},
|
||||
[channel, target] {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("User %1 couldn't be blocked, an unknown "
|
||||
"error occurred!")
|
||||
.arg(target)));
|
||||
});
|
||||
},
|
||||
[channel{ctx.channel}, target] {
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("User %1 couldn't be blocked, no "
|
||||
"user with that name found!")
|
||||
.arg(target)));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString ignoreUser(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"Ignore command has been renamed to /block, please use it from "
|
||||
"now on as /ignore is going to be removed soon."));
|
||||
|
||||
return blockUser(ctx);
|
||||
}
|
||||
|
||||
QString unblockUser(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /unblock command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage("Usage: /unblock <user>"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
if (currentUser->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("You must be logged in to unblock someone!"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto target = ctx.words.at(1);
|
||||
stripChannelName(target);
|
||||
|
||||
getHelix()->getUserByName(
|
||||
target,
|
||||
[currentUser, channel{ctx.channel}, target](const auto &targetUser) {
|
||||
getApp()->accounts->twitch.getCurrent()->unblockUser(
|
||||
targetUser.id, nullptr,
|
||||
[channel, target, targetUser] {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("You successfully unblocked user %1")
|
||||
.arg(target)));
|
||||
},
|
||||
[channel, target] {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("User %1 couldn't be unblocked, an unknown "
|
||||
"error occurred!")
|
||||
.arg(target)));
|
||||
});
|
||||
},
|
||||
[channel{ctx.channel}, target] {
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("User %1 couldn't be unblocked, "
|
||||
"no user with that name found!")
|
||||
.arg(target)));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString unignoreUser(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"Unignore command has been renamed to /unblock, please use it "
|
||||
"from now on as /unignore is going to be removed soon."));
|
||||
return unblockUser(ctx);
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
25
src/controllers/commands/builtin/twitch/Block.hpp
Normal file
25
src/controllers/commands/builtin/twitch/Block.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
/// /block
|
||||
QString blockUser(const CommandContext &ctx);
|
||||
|
||||
/// /ignore
|
||||
QString ignoreUser(const CommandContext &ctx);
|
||||
|
||||
/// /unblock
|
||||
QString unblockUser(const CommandContext &ctx);
|
||||
|
||||
/// /unignore
|
||||
QString unignoreUser(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
143
src/controllers/commands/builtin/twitch/Chatters.cpp
Normal file
143
src/controllers/commands/builtin/twitch/Chatters.cpp
Normal file
|
@ -0,0 +1,143 @@
|
|||
#include "controllers/commands/builtin/twitch/Chatters.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/Env.hpp"
|
||||
#include "common/Literals.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QLoggingCategory>
|
||||
#include <QString>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace chatterino;
|
||||
|
||||
QString formatChattersError(HelixGetChattersError error, const QString &message)
|
||||
{
|
||||
using Error = HelixGetChattersError;
|
||||
|
||||
QString errorMessage = QString("Failed to get chatter count - ");
|
||||
|
||||
switch (error)
|
||||
{
|
||||
case Error::Forwarded: {
|
||||
errorMessage += message;
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::UserMissingScope: {
|
||||
errorMessage += "Missing required scope. "
|
||||
"Re-login with your "
|
||||
"account and try again.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::UserNotAuthorized: {
|
||||
errorMessage += "You must have moderator permissions to "
|
||||
"use this command.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Unknown: {
|
||||
errorMessage += "An unknown error has occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString chatters(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /chatters command only works in Twitch Channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
// Refresh chatter list via helix api for mods
|
||||
getHelix()->getChatters(
|
||||
ctx.twitchChannel->roomId(),
|
||||
getApp()->accounts->twitch.getCurrent()->getUserId(), 1,
|
||||
[channel{ctx.channel}](auto result) {
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("Chatter count: %1")
|
||||
.arg(localizeNumbers(result.total))));
|
||||
},
|
||||
[channel{ctx.channel}](auto error, auto message) {
|
||||
auto errorMessage = formatChattersError(error, message);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString testChatters(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /test-chatters command only works in Twitch Channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
getHelix()->getChatters(
|
||||
ctx.twitchChannel->roomId(),
|
||||
getApp()->accounts->twitch.getCurrent()->getUserId(), 5000,
|
||||
[channel{ctx.channel}, twitchChannel{ctx.twitchChannel}](auto result) {
|
||||
QStringList entries;
|
||||
for (const auto &username : result.chatters)
|
||||
{
|
||||
entries << username;
|
||||
}
|
||||
|
||||
QString prefix = "Chatters ";
|
||||
|
||||
if (result.total > 5000)
|
||||
{
|
||||
prefix += QString("(5000/%1):").arg(result.total);
|
||||
}
|
||||
else
|
||||
{
|
||||
prefix += QString("(%1):").arg(result.total);
|
||||
}
|
||||
|
||||
MessageBuilder builder;
|
||||
TwitchMessageBuilder::listOfUsersSystemMessage(
|
||||
prefix, entries, twitchChannel, &builder);
|
||||
|
||||
channel->addMessage(builder.release());
|
||||
},
|
||||
[channel{ctx.channel}](auto error, auto message) {
|
||||
auto errorMessage = formatChattersError(error, message);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
17
src/controllers/commands/builtin/twitch/Chatters.hpp
Normal file
17
src/controllers/commands/builtin/twitch/Chatters.hpp
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString chatters(const CommandContext &ctx);
|
||||
|
||||
QString testChatters(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
162
src/controllers/commands/builtin/twitch/DeleteMessages.cpp
Normal file
162
src/controllers/commands/builtin/twitch/DeleteMessages.cpp
Normal file
|
@ -0,0 +1,162 @@
|
|||
#include "controllers/commands/builtin/twitch/DeleteMessages.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/NetworkResult.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
|
||||
#include <QUuid>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace chatterino;
|
||||
|
||||
QString deleteMessages(TwitchChannel *twitchChannel, const QString &messageID)
|
||||
{
|
||||
const auto *commandName = messageID.isEmpty() ? "/clear" : "/delete";
|
||||
|
||||
auto user = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
// Avoid Helix calls without Client ID and/or OAuth Token
|
||||
if (user->isAnon())
|
||||
{
|
||||
twitchChannel->addMessage(makeSystemMessage(
|
||||
QString("You must be logged in to use the %1 command.")
|
||||
.arg(commandName)));
|
||||
return "";
|
||||
}
|
||||
|
||||
getHelix()->deleteChatMessages(
|
||||
twitchChannel->roomId(), user->getUserId(), messageID,
|
||||
[]() {
|
||||
// Success handling, we do nothing: IRC/pubsub-edge will dispatch the correct
|
||||
// events to update state for us.
|
||||
},
|
||||
[twitchChannel, messageID](auto error, auto message) {
|
||||
QString errorMessage = QString("Failed to delete chat messages - ");
|
||||
|
||||
switch (error)
|
||||
{
|
||||
case HelixDeleteChatMessagesError::UserMissingScope: {
|
||||
errorMessage +=
|
||||
"Missing required scope. Re-login with your "
|
||||
"account and try again.";
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixDeleteChatMessagesError::UserNotAuthorized: {
|
||||
errorMessage +=
|
||||
"you don't have permission to perform that action.";
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixDeleteChatMessagesError::MessageUnavailable: {
|
||||
// Override default message prefix to match with IRC message format
|
||||
errorMessage =
|
||||
QString("The message %1 does not exist, was deleted, "
|
||||
"or is too old to be deleted.")
|
||||
.arg(messageID);
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixDeleteChatMessagesError::UserNotAuthenticated: {
|
||||
errorMessage += "you need to re-authenticate.";
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixDeleteChatMessagesError::Forwarded: {
|
||||
errorMessage += message;
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixDeleteChatMessagesError::Unknown:
|
||||
default: {
|
||||
errorMessage += "An unknown error has occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
twitchChannel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString deleteAllMessages(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /clear command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
return deleteMessages(ctx.twitchChannel, QString());
|
||||
}
|
||||
|
||||
QString deleteOneMessage(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
// This is a wrapper over the Helix delete messages endpoint
|
||||
// We use this to ensure the user gets better error messages for missing or malformed arguments
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /delete command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("Usage: /delete <msg-id> - Deletes the "
|
||||
"specified message."));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto messageID = ctx.words.at(1);
|
||||
auto uuid = QUuid(messageID);
|
||||
if (uuid.isNull())
|
||||
{
|
||||
// The message id must be a valid UUID
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
QString("Invalid msg-id: \"%1\"").arg(messageID)));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto msg = ctx.channel->findMessage(messageID);
|
||||
if (msg != nullptr)
|
||||
{
|
||||
if (msg->loginName == ctx.channel->getName() &&
|
||||
!ctx.channel->isBroadcaster())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"You cannot delete the broadcaster's messages unless "
|
||||
"you are the broadcaster."));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
return deleteMessages(ctx.twitchChannel, messageID);
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
19
src/controllers/commands/builtin/twitch/DeleteMessages.hpp
Normal file
19
src/controllers/commands/builtin/twitch/DeleteMessages.hpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
/// /clear
|
||||
QString deleteAllMessages(const CommandContext &ctx);
|
||||
|
||||
/// /delete
|
||||
QString deleteOneMessage(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
94
src/controllers/commands/builtin/twitch/GetModerators.cpp
Normal file
94
src/controllers/commands/builtin/twitch/GetModerators.cpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
#include "controllers/commands/builtin/twitch/GetModerators.hpp"
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace chatterino;
|
||||
|
||||
QString formatModsError(HelixGetModeratorsError error, const QString &message)
|
||||
{
|
||||
using Error = HelixGetModeratorsError;
|
||||
|
||||
QString errorMessage = QString("Failed to get moderators - ");
|
||||
|
||||
switch (error)
|
||||
{
|
||||
case Error::Forwarded: {
|
||||
errorMessage += message;
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::UserMissingScope: {
|
||||
errorMessage += "Missing required scope. "
|
||||
"Re-login with your "
|
||||
"account and try again.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::UserNotAuthorized: {
|
||||
errorMessage +=
|
||||
"Due to Twitch restrictions, "
|
||||
"this command can only be used by the broadcaster. "
|
||||
"To see the list of mods you must use the Twitch website.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Unknown: {
|
||||
errorMessage += "An unknown error has occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString getModerators(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /mods command only works in Twitch Channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
getHelix()->getModerators(
|
||||
ctx.twitchChannel->roomId(), 500,
|
||||
[channel{ctx.channel}, twitchChannel{ctx.twitchChannel}](auto result) {
|
||||
if (result.empty())
|
||||
{
|
||||
channel->addMessage(makeSystemMessage(
|
||||
"This channel does not have any moderators."));
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: sort results?
|
||||
|
||||
MessageBuilder builder;
|
||||
TwitchMessageBuilder::listOfUsersSystemMessage(
|
||||
"The moderators of this channel are", result, twitchChannel,
|
||||
&builder);
|
||||
channel->addMessage(builder.release());
|
||||
},
|
||||
[channel{ctx.channel}](auto error, auto message) {
|
||||
auto errorMessage = formatModsError(error, message);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
16
src/controllers/commands/builtin/twitch/GetModerators.hpp
Normal file
16
src/controllers/commands/builtin/twitch/GetModerators.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
/// /mods
|
||||
QString getModerators(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
124
src/controllers/commands/builtin/twitch/GetVIPs.cpp
Normal file
124
src/controllers/commands/builtin/twitch/GetVIPs.cpp
Normal file
|
@ -0,0 +1,124 @@
|
|||
#include "controllers/commands/builtin/twitch/GetVIPs.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
#include "util/Twitch.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace chatterino;
|
||||
|
||||
QString formatGetVIPsError(HelixListVIPsError error, const QString &message)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString getVIPs(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /vips command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
if (currentUser->isAnon())
|
||||
{
|
||||
ctx.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(
|
||||
ctx.twitchChannel->roomId(),
|
||||
[channel{ctx.channel}, twitchChannel{ctx.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");
|
||||
|
||||
// TODO: sort results?
|
||||
MessageBuilder builder;
|
||||
TwitchMessageBuilder::listOfUsersSystemMessage(
|
||||
messagePrefix, vipList, twitchChannel, &builder);
|
||||
|
||||
channel->addMessage(builder.release());
|
||||
},
|
||||
[channel{ctx.channel}](auto error, auto message) {
|
||||
auto errorMessage = formatGetVIPsError(error, message);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
16
src/controllers/commands/builtin/twitch/GetVIPs.hpp
Normal file
16
src/controllers/commands/builtin/twitch/GetVIPs.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
/// /vips
|
||||
QString getVIPs(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
220
src/controllers/commands/builtin/twitch/Raid.cpp
Normal file
220
src/controllers/commands/builtin/twitch/Raid.cpp
Normal file
|
@ -0,0 +1,220 @@
|
|||
#include "controllers/commands/builtin/twitch/Raid.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "util/Twitch.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace chatterino;
|
||||
|
||||
QString formatStartRaidError(HelixStartRaidError error, const QString &message)
|
||||
{
|
||||
QString errorMessage = QString("Failed to start a raid - ");
|
||||
|
||||
using Error = HelixStartRaidError;
|
||||
|
||||
switch (error)
|
||||
{
|
||||
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: {
|
||||
errorMessage += "You must be the broadcaster "
|
||||
"to start a raid.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::CantRaidYourself: {
|
||||
errorMessage += "A channel cannot raid itself.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Ratelimited: {
|
||||
errorMessage += "You are being ratelimited "
|
||||
"by Twitch. Try "
|
||||
"again in a few seconds.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Forwarded: {
|
||||
errorMessage += message;
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Unknown:
|
||||
default: {
|
||||
errorMessage += "An unknown error has occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
QString formatCancelRaidError(HelixCancelRaidError error,
|
||||
const QString &message)
|
||||
{
|
||||
QString errorMessage = QString("Failed to cancel the raid - ");
|
||||
|
||||
using Error = HelixCancelRaidError;
|
||||
|
||||
switch (error)
|
||||
{
|
||||
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: {
|
||||
errorMessage += "You must be the broadcaster "
|
||||
"to cancel the raid.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::NoRaidPending: {
|
||||
errorMessage += "You don't have an active raid.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Ratelimited: {
|
||||
errorMessage += "You are being ratelimited by Twitch. Try "
|
||||
"again in a few seconds.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Forwarded: {
|
||||
errorMessage += message;
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Unknown:
|
||||
default: {
|
||||
errorMessage += "An unknown error has occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString startRaid(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /raid command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("Usage: \"/raid <username>\" - Raid a user. "
|
||||
"Only the broadcaster can start a raid."));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
if (currentUser->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("You must be logged in to start a raid!"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto target = ctx.words.at(1);
|
||||
stripChannelName(target);
|
||||
|
||||
getHelix()->getUserByName(
|
||||
target,
|
||||
[twitchChannel{ctx.twitchChannel},
|
||||
channel{ctx.channel}](const HelixUser &targetUser) {
|
||||
getHelix()->startRaid(
|
||||
twitchChannel->roomId(), targetUser.id,
|
||||
[channel, targetUser] {
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("You started to raid %1.")
|
||||
.arg(targetUser.displayName)));
|
||||
},
|
||||
[channel, targetUser](auto error, auto message) {
|
||||
auto errorMessage = formatStartRaidError(error, message);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
},
|
||||
[channel{ctx.channel}, target] {
|
||||
// Equivalent error from IRC
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("Invalid username: %1").arg(target)));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString cancelRaid(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /unraid command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() != 1)
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("Usage: \"/unraid\" - Cancel the current raid. "
|
||||
"Only the broadcaster can cancel the raid."));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
if (currentUser->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("You must be logged in to cancel the raid!"));
|
||||
return "";
|
||||
}
|
||||
|
||||
getHelix()->cancelRaid(
|
||||
ctx.twitchChannel->roomId(),
|
||||
[channel{ctx.channel}] {
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("You cancelled the raid.")));
|
||||
},
|
||||
[channel{ctx.channel}](auto error, auto message) {
|
||||
auto errorMessage = formatCancelRaidError(error, message);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
19
src/controllers/commands/builtin/twitch/Raid.hpp
Normal file
19
src/controllers/commands/builtin/twitch/Raid.hpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
/// /raid
|
||||
QString startRaid(const CommandContext &ctx);
|
||||
|
||||
/// /unraid
|
||||
QString cancelRaid(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
122
src/controllers/commands/builtin/twitch/RemoveModerator.cpp
Normal file
122
src/controllers/commands/builtin/twitch/RemoveModerator.cpp
Normal file
|
@ -0,0 +1,122 @@
|
|||
#include "controllers/commands/builtin/twitch/RemoveModerator.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
#include "util/Twitch.hpp"
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString removeModerator(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /unmod command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"Usage: \"/unmod <username>\" - Revoke moderator status from a "
|
||||
"user. Use \"/mods\" to list the moderators of this channel."));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
if (currentUser->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("You must be logged in to unmod someone!"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto target = ctx.words.at(1);
|
||||
stripChannelName(target);
|
||||
|
||||
getHelix()->getUserByName(
|
||||
target,
|
||||
[twitchChannel{ctx.twitchChannel},
|
||||
channel{ctx.channel}](const HelixUser &targetUser) {
|
||||
getHelix()->removeChannelModerator(
|
||||
twitchChannel->roomId(), targetUser.id,
|
||||
[channel, targetUser] {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("You have removed %1 as a moderator of "
|
||||
"this channel.")
|
||||
.arg(targetUser.displayName)));
|
||||
},
|
||||
[channel, targetUser](auto error, auto message) {
|
||||
QString errorMessage =
|
||||
QString("Failed to remove channel moderator - ");
|
||||
|
||||
using Error = HelixRemoveChannelModeratorError;
|
||||
|
||||
switch (error)
|
||||
{
|
||||
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::Ratelimited: {
|
||||
errorMessage +=
|
||||
"You are being ratelimited by Twitch. Try "
|
||||
"again in a few seconds.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::TargetNotModded: {
|
||||
// Equivalent irc error
|
||||
errorMessage +=
|
||||
QString("%1 is not a moderator of this "
|
||||
"channel.")
|
||||
.arg(targetUser.displayName);
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Forwarded: {
|
||||
errorMessage += message;
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Unknown:
|
||||
default: {
|
||||
errorMessage += "An unknown error has occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
},
|
||||
[channel{ctx.channel}, target] {
|
||||
// Equivalent error from IRC
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("Invalid username: %1").arg(target)));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
16
src/controllers/commands/builtin/twitch/RemoveModerator.hpp
Normal file
16
src/controllers/commands/builtin/twitch/RemoveModerator.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
/// /unmod
|
||||
QString removeModerator(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
112
src/controllers/commands/builtin/twitch/RemoveVIP.cpp
Normal file
112
src/controllers/commands/builtin/twitch/RemoveVIP.cpp
Normal file
|
@ -0,0 +1,112 @@
|
|||
#include "controllers/commands/builtin/twitch/RemoveVIP.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
#include "util/Twitch.hpp"
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString removeVIP(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /unvip command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"Usage: \"/unvip <username>\" - Revoke VIP status from a user. "
|
||||
"Use \"/vips\" to list the VIPs of this channel."));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
if (currentUser->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("You must be logged in to UnVIP someone!"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto target = ctx.words.at(1);
|
||||
stripChannelName(target);
|
||||
|
||||
getHelix()->getUserByName(
|
||||
target,
|
||||
[twitchChannel{ctx.twitchChannel},
|
||||
channel{ctx.channel}](const HelixUser &targetUser) {
|
||||
getHelix()->removeChannelVIP(
|
||||
twitchChannel->roomId(), targetUser.id,
|
||||
[channel, targetUser] {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("You have removed %1 as a VIP of this channel.")
|
||||
.arg(targetUser.displayName)));
|
||||
},
|
||||
[channel, targetUser](auto error, auto message) {
|
||||
QString errorMessage = QString("Failed to remove VIP - ");
|
||||
|
||||
using Error = HelixRemoveChannelVIPError;
|
||||
|
||||
switch (error)
|
||||
{
|
||||
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::Ratelimited: {
|
||||
errorMessage +=
|
||||
"You are being ratelimited by Twitch. Try "
|
||||
"again in a few seconds.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Forwarded: {
|
||||
// These are actually the IRC equivalents, so we can ditch the prefix
|
||||
errorMessage = message;
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Unknown:
|
||||
default: {
|
||||
errorMessage += "An unknown error has occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
},
|
||||
[channel{ctx.channel}, target] {
|
||||
// Equivalent error from IRC
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("Invalid username: %1").arg(target)));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
16
src/controllers/commands/builtin/twitch/RemoveVIP.hpp
Normal file
16
src/controllers/commands/builtin/twitch/RemoveVIP.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
/// /unvip
|
||||
QString removeVIP(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
63
src/controllers/commands/builtin/twitch/SendReply.cpp
Normal file
63
src/controllers/commands/builtin/twitch/SendReply.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
#include "controllers/commands/builtin/twitch/SendReply.hpp"
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "messages/MessageThread.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "util/Twitch.hpp"
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString sendReply(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /reply command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() < 3)
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("Usage: /reply <username> <message>"));
|
||||
return "";
|
||||
}
|
||||
|
||||
QString username = ctx.words[1];
|
||||
stripChannelName(username);
|
||||
|
||||
auto snapshot = ctx.twitchChannel->getMessageSnapshot();
|
||||
for (auto it = snapshot.rbegin(); it != snapshot.rend(); ++it)
|
||||
{
|
||||
const auto &msg = *it;
|
||||
if (msg->loginName.compare(username, Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
// found most recent message by user
|
||||
if (msg->replyThread == nullptr)
|
||||
{
|
||||
// prepare thread if one does not exist
|
||||
auto thread = std::make_shared<MessageThread>(msg);
|
||||
ctx.twitchChannel->addReplyThread(thread);
|
||||
}
|
||||
|
||||
QString reply = ctx.words.mid(2).join(" ");
|
||||
ctx.twitchChannel->sendReply(reply, msg->id);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("A message from that user wasn't found"));
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
15
src/controllers/commands/builtin/twitch/SendReply.hpp
Normal file
15
src/controllers/commands/builtin/twitch/SendReply.hpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString sendReply(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
258
src/controllers/commands/builtin/twitch/SendWhisper.cpp
Normal file
258
src/controllers/commands/builtin/twitch/SendWhisper.cpp
Normal file
|
@ -0,0 +1,258 @@
|
|||
#include "controllers/commands/builtin/twitch/SendWhisper.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/LinkParser.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "providers/irc/IrcChannel2.hpp"
|
||||
#include "providers/irc/IrcServer.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/Twitch.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace chatterino;
|
||||
|
||||
QString formatWhisperError(HelixWhisperError error, const QString &message)
|
||||
{
|
||||
using Error = HelixWhisperError;
|
||||
|
||||
QString errorMessage = "Failed to send whisper - ";
|
||||
|
||||
switch (error)
|
||||
{
|
||||
case Error::NoVerifiedPhone: {
|
||||
errorMessage += "Due to Twitch restrictions, you are now "
|
||||
"required to have a verified phone number "
|
||||
"to send whispers. You can add a phone "
|
||||
"number in Twitch settings. "
|
||||
"https://www.twitch.tv/settings/security";
|
||||
};
|
||||
break;
|
||||
|
||||
case Error::RecipientBlockedUser: {
|
||||
errorMessage += "The recipient doesn't allow whispers "
|
||||
"from strangers or you directly.";
|
||||
};
|
||||
break;
|
||||
|
||||
case Error::WhisperSelf: {
|
||||
errorMessage += "You cannot whisper yourself.";
|
||||
};
|
||||
break;
|
||||
|
||||
case Error::Forwarded: {
|
||||
errorMessage += message;
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Ratelimited: {
|
||||
errorMessage += "You may only whisper a maximum of 40 "
|
||||
"unique recipients per day. Within the "
|
||||
"per day limit, you may whisper a "
|
||||
"maximum of 3 whispers per second and "
|
||||
"a maximum of 100 whispers per minute.";
|
||||
}
|
||||
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::Unknown: {
|
||||
errorMessage += "An unknown error has occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
bool appendWhisperMessageWordsLocally(const QStringList &words)
|
||||
{
|
||||
auto *app = getApp();
|
||||
|
||||
MessageBuilder b;
|
||||
|
||||
b.emplace<TimestampElement>();
|
||||
b.emplace<TextElement>(app->accounts->twitch.getCurrent()->getUserName(),
|
||||
MessageElementFlag::Text, MessageColor::Text,
|
||||
FontStyle::ChatMediumBold);
|
||||
b.emplace<TextElement>("->", MessageElementFlag::Text,
|
||||
getApp()->themes->messages.textColors.system);
|
||||
b.emplace<TextElement>(words[1] + ":", MessageElementFlag::Text,
|
||||
MessageColor::Text, FontStyle::ChatMediumBold);
|
||||
|
||||
const auto &acc = app->accounts->twitch.getCurrent();
|
||||
const auto &accemotes = *acc->accessEmotes();
|
||||
const auto &bttvemotes = app->twitch->getBttvEmotes();
|
||||
const auto &ffzemotes = app->twitch->getFfzEmotes();
|
||||
auto flags = MessageElementFlags();
|
||||
auto emote = std::optional<EmotePtr>{};
|
||||
for (int i = 2; i < words.length(); i++)
|
||||
{
|
||||
{ // Twitch emote
|
||||
auto it = accemotes.emotes.find({words[i]});
|
||||
if (it != accemotes.emotes.end())
|
||||
{
|
||||
b.emplace<EmoteElement>(it->second,
|
||||
MessageElementFlag::TwitchEmote);
|
||||
continue;
|
||||
}
|
||||
} // Twitch emote
|
||||
|
||||
{ // bttv/ffz emote
|
||||
if ((emote = bttvemotes.emote({words[i]})))
|
||||
{
|
||||
flags = MessageElementFlag::BttvEmote;
|
||||
}
|
||||
else if ((emote = ffzemotes.emote({words[i]})))
|
||||
{
|
||||
flags = MessageElementFlag::FfzEmote;
|
||||
}
|
||||
if (emote)
|
||||
{
|
||||
b.emplace<EmoteElement>(*emote, flags);
|
||||
continue;
|
||||
}
|
||||
} // bttv/ffz emote
|
||||
{ // emoji/text
|
||||
for (auto &variant : app->emotes->emojis.parse(words[i]))
|
||||
{
|
||||
constexpr const static struct {
|
||||
void operator()(EmotePtr emote, MessageBuilder &b) const
|
||||
{
|
||||
b.emplace<EmoteElement>(emote,
|
||||
MessageElementFlag::EmojiAll);
|
||||
}
|
||||
void operator()(const QString &string,
|
||||
MessageBuilder &b) const
|
||||
{
|
||||
LinkParser parser(string);
|
||||
if (parser.result())
|
||||
{
|
||||
b.addLink(*parser.result());
|
||||
}
|
||||
else
|
||||
{
|
||||
b.emplace<TextElement>(string,
|
||||
MessageElementFlag::Text);
|
||||
}
|
||||
}
|
||||
} visitor;
|
||||
boost::apply_visitor(
|
||||
[&b](auto &&arg) {
|
||||
visitor(arg, b);
|
||||
},
|
||||
variant);
|
||||
} // emoji/text
|
||||
}
|
||||
}
|
||||
|
||||
b->flags.set(MessageFlag::DoNotTriggerNotification);
|
||||
b->flags.set(MessageFlag::Whisper);
|
||||
auto messagexD = b.release();
|
||||
|
||||
app->twitch->whispersChannel->addMessage(messagexD);
|
||||
|
||||
auto overrideFlags = std::optional<MessageFlags>(messagexD->flags);
|
||||
overrideFlags->set(MessageFlag::DoNotLog);
|
||||
|
||||
if (getSettings()->inlineWhispers &&
|
||||
!(getSettings()->streamerModeSuppressInlineWhispers &&
|
||||
isInStreamerMode()))
|
||||
{
|
||||
app->twitch->forEachChannel(
|
||||
[&messagexD, overrideFlags](ChannelPtr _channel) {
|
||||
_channel->addMessage(messagexD, overrideFlags);
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString sendWhisper(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() < 3)
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("Usage: /w <username> <message>"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
if (currentUser->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("You must be logged in to send a whisper!"));
|
||||
return "";
|
||||
}
|
||||
auto target = ctx.words.at(1);
|
||||
stripChannelName(target);
|
||||
auto message = ctx.words.mid(2).join(' ');
|
||||
if (ctx.channel->isTwitchChannel())
|
||||
{
|
||||
getHelix()->getUserByName(
|
||||
target,
|
||||
[channel{ctx.channel}, currentUser, target, message,
|
||||
words{ctx.words}](const auto &targetUser) {
|
||||
getHelix()->sendWhisper(
|
||||
currentUser->getUserId(), targetUser.id, message,
|
||||
[words] {
|
||||
appendWhisperMessageWordsLocally(words);
|
||||
},
|
||||
[channel, target, targetUser](auto error, auto message) {
|
||||
auto errorMessage = formatWhisperError(error, message);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
},
|
||||
[channel{ctx.channel}] {
|
||||
channel->addMessage(
|
||||
makeSystemMessage("No user matching that username."));
|
||||
});
|
||||
return "";
|
||||
}
|
||||
|
||||
// we must be on IRC
|
||||
auto *ircChannel = dynamic_cast<IrcChannel *>(ctx.channel.get());
|
||||
if (ircChannel == nullptr)
|
||||
{
|
||||
// give up
|
||||
return "";
|
||||
}
|
||||
|
||||
auto *server = ircChannel->server();
|
||||
server->sendWhisper(target, message);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
15
src/controllers/commands/builtin/twitch/SendWhisper.hpp
Normal file
15
src/controllers/commands/builtin/twitch/SendWhisper.hpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString sendWhisper(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
136
src/controllers/commands/builtin/twitch/StartCommercial.cpp
Normal file
136
src/controllers/commands/builtin/twitch/StartCommercial.cpp
Normal file
|
@ -0,0 +1,136 @@
|
|||
#include "controllers/commands/builtin/twitch/StartCommercial.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace chatterino;
|
||||
|
||||
QString formatStartCommercialError(HelixStartCommercialError error,
|
||||
const QString &message)
|
||||
{
|
||||
using Error = HelixStartCommercialError;
|
||||
|
||||
QString errorMessage = "Failed to start commercial - ";
|
||||
|
||||
switch (error)
|
||||
{
|
||||
case Error::UserMissingScope: {
|
||||
errorMessage += "Missing required scope. Re-login with your "
|
||||
"account and try again.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::TokenMustMatchBroadcaster: {
|
||||
errorMessage += "Only the broadcaster of the channel can run "
|
||||
"commercials.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::BroadcasterNotStreaming: {
|
||||
errorMessage += "You must be streaming live to run "
|
||||
"commercials.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::MissingLengthParameter: {
|
||||
errorMessage += "Command must include a desired commercial break "
|
||||
"length that is greater than zero.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Ratelimited: {
|
||||
errorMessage += "You must wait until your cooldown period "
|
||||
"expires before you can run another "
|
||||
"commercial.";
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Forwarded: {
|
||||
errorMessage += message;
|
||||
}
|
||||
break;
|
||||
|
||||
case Error::Unknown:
|
||||
default: {
|
||||
errorMessage +=
|
||||
QString("An unknown error has occurred (%1).").arg(message);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString startCommercial(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /commercial command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
|
||||
const auto *usageStr = "Usage: \"/commercial <length>\" - Starts a "
|
||||
"commercial with the "
|
||||
"specified duration for the current "
|
||||
"channel. Valid length options "
|
||||
"are 30, 60, 90, 120, 150, and 180 seconds.";
|
||||
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(usageStr));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto user = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
// Avoid Helix calls without Client ID and/or OAuth Token
|
||||
if (user->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"You must be logged in to use the /commercial command"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto broadcasterID = ctx.twitchChannel->roomId();
|
||||
auto length = ctx.words.at(1).toInt();
|
||||
|
||||
getHelix()->startCommercial(
|
||||
broadcasterID, length,
|
||||
[channel{ctx.channel}](auto response) {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("Starting %1 second long commercial break. "
|
||||
"Keep in mind you are still "
|
||||
"live and not all viewers will receive a "
|
||||
"commercial. "
|
||||
"You may run another commercial in %2 seconds.")
|
||||
.arg(response.length)
|
||||
.arg(response.retryAfter)));
|
||||
},
|
||||
[channel{ctx.channel}](auto error, auto message) {
|
||||
auto errorMessage = formatStartCommercialError(error, message);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
16
src/controllers/commands/builtin/twitch/StartCommercial.hpp
Normal file
16
src/controllers/commands/builtin/twitch/StartCommercial.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
/// /commercial
|
||||
QString startCommercial(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
124
src/controllers/commands/builtin/twitch/Unban.cpp
Normal file
124
src/controllers/commands/builtin/twitch/Unban.cpp
Normal file
|
@ -0,0 +1,124 @@
|
|||
#include "Application.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/builtin/twitch/Ban.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "util/Twitch.hpp"
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString unbanUser(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
auto commandName = ctx.words.at(0).toLower();
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
QString("The %1 command only works in Twitch channels")
|
||||
.arg(commandName)));
|
||||
return "";
|
||||
}
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
QString("Usage: \"%1 <username>\" - Removes a ban on a user.")
|
||||
.arg(commandName)));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
if (currentUser->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("You must be logged in to unban someone!"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto target = ctx.words.at(1);
|
||||
stripChannelName(target);
|
||||
|
||||
getHelix()->getUserByName(
|
||||
target,
|
||||
[channel{ctx.channel}, currentUser, twitchChannel{ctx.twitchChannel},
|
||||
target](const auto &targetUser) {
|
||||
getHelix()->unbanUser(
|
||||
twitchChannel->roomId(), currentUser->getUserId(),
|
||||
targetUser.id,
|
||||
[] {
|
||||
// No response for unbans, they're emitted over pubsub/IRC instead
|
||||
},
|
||||
[channel, target, targetUser](auto error, auto message) {
|
||||
using Error = HelixUnbanUserError;
|
||||
|
||||
QString errorMessage = QString("Failed to unban user - ");
|
||||
|
||||
switch (error)
|
||||
{
|
||||
case Error::ConflictingOperation: {
|
||||
errorMessage +=
|
||||
"There was a conflicting ban operation on "
|
||||
"this user. Please try again.";
|
||||
}
|
||||
break;
|
||||
|
||||
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::TargetNotBanned: {
|
||||
// Equivalent IRC error
|
||||
errorMessage =
|
||||
QString("%1 is not banned from this channel.")
|
||||
.arg(targetUser.displayName);
|
||||
}
|
||||
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::Unknown: {
|
||||
errorMessage += "An unknown error has occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
},
|
||||
[channel{ctx.channel}, target] {
|
||||
// Equivalent error from IRC
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("Invalid username: %1").arg(target)));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
16
src/controllers/commands/builtin/twitch/Unban.hpp
Normal file
16
src/controllers/commands/builtin/twitch/Unban.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
/// /unban
|
||||
QString unbanUser(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
121
src/controllers/commands/builtin/twitch/UpdateChannel.cpp
Normal file
121
src/controllers/commands/builtin/twitch/UpdateChannel.cpp
Normal file
|
@ -0,0 +1,121 @@
|
|||
#include "controllers/commands/builtin/twitch/UpdateChannel.hpp"
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/NetworkResult.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString setTitle(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("Usage: /settitle <stream title>"));
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("Unable to set title of non-Twitch channel."));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto status = ctx.twitchChannel->accessStreamStatus();
|
||||
auto title = ctx.words.mid(1).join(" ");
|
||||
getHelix()->updateChannel(
|
||||
ctx.twitchChannel->roomId(), "", "", title,
|
||||
[channel{ctx.channel}, title](const auto &result) {
|
||||
(void)result;
|
||||
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("Updated title to %1").arg(title)));
|
||||
},
|
||||
[channel{ctx.channel}] {
|
||||
channel->addMessage(
|
||||
makeSystemMessage("Title update failed! Are you "
|
||||
"missing the required scope?"));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString setGame(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.words.size() < 2)
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("Usage: /setgame <stream game>"));
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("Unable to set game of non-Twitch channel."));
|
||||
return "";
|
||||
}
|
||||
|
||||
const auto gameName = ctx.words.mid(1).join(" ");
|
||||
|
||||
getHelix()->searchGames(
|
||||
gameName,
|
||||
[channel{ctx.channel}, twitchChannel{ctx.twitchChannel},
|
||||
gameName](const std::vector<HelixGame> &games) {
|
||||
if (games.empty())
|
||||
{
|
||||
channel->addMessage(makeSystemMessage("Game not found."));
|
||||
return;
|
||||
}
|
||||
|
||||
auto matchedGame = games.at(0);
|
||||
|
||||
if (games.size() > 1)
|
||||
{
|
||||
// NOTE: Improvements could be made with 'fuzzy string matching' code here
|
||||
// attempt to find the best looking game by comparing exactly with lowercase values
|
||||
for (const auto &game : games)
|
||||
{
|
||||
if (game.name.toLower() == gameName.toLower())
|
||||
{
|
||||
matchedGame = game;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto status = twitchChannel->accessStreamStatus();
|
||||
getHelix()->updateChannel(
|
||||
twitchChannel->roomId(), matchedGame.id, "", "",
|
||||
[channel, games, matchedGame](const NetworkResult &) {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("Updated game to %1").arg(matchedGame.name)));
|
||||
},
|
||||
[channel] {
|
||||
channel->addMessage(
|
||||
makeSystemMessage("Game update failed! Are you "
|
||||
"missing the required scope?"));
|
||||
});
|
||||
},
|
||||
[channel{ctx.channel}] {
|
||||
channel->addMessage(makeSystemMessage("Failed to look up game."));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
16
src/controllers/commands/builtin/twitch/UpdateChannel.hpp
Normal file
16
src/controllers/commands/builtin/twitch/UpdateChannel.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString setTitle(const CommandContext &ctx);
|
||||
QString setGame(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
99
src/controllers/commands/builtin/twitch/UpdateColor.cpp
Normal file
99
src/controllers/commands/builtin/twitch/UpdateColor.cpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
#include "controllers/commands/builtin/twitch/UpdateColor.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "util/Twitch.hpp"
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString updateUserColor(const CommandContext &ctx)
|
||||
{
|
||||
if (ctx.channel == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!ctx.channel->isTwitchChannel())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /color command only works in Twitch channels"));
|
||||
return "";
|
||||
}
|
||||
auto user = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
// Avoid Helix calls without Client ID and/or OAuth Token
|
||||
if (user->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"You must be logged in to use the /color command"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto colorString = ctx.words.value(1);
|
||||
|
||||
if (colorString.isEmpty())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
QString("Usage: /color <color> - Color must be one of Twitch's "
|
||||
"supported colors (%1) or a hex code (#000000) if you "
|
||||
"have Turbo or Prime.")
|
||||
.arg(VALID_HELIX_COLORS.join(", "))));
|
||||
return "";
|
||||
}
|
||||
|
||||
cleanHelixColorName(colorString);
|
||||
|
||||
getHelix()->updateUserChatColor(
|
||||
user->getUserId(), colorString,
|
||||
[colorString, channel{ctx.channel}] {
|
||||
QString successMessage =
|
||||
QString("Your color has been changed to %1.").arg(colorString);
|
||||
channel->addMessage(makeSystemMessage(successMessage));
|
||||
},
|
||||
[colorString, channel{ctx.channel}](auto error, auto message) {
|
||||
QString errorMessage =
|
||||
QString("Failed to change color to %1 - ").arg(colorString);
|
||||
|
||||
switch (error)
|
||||
{
|
||||
case HelixUpdateUserChatColorError::UserMissingScope: {
|
||||
errorMessage +=
|
||||
"Missing required scope. Re-login with your "
|
||||
"account and try again.";
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixUpdateUserChatColorError::InvalidColor: {
|
||||
errorMessage += QString("Color must be one of Twitch's "
|
||||
"supported colors (%1) or a "
|
||||
"hex code (#000000) if you "
|
||||
"have Turbo or Prime.")
|
||||
.arg(VALID_HELIX_COLORS.join(", "));
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixUpdateUserChatColorError::Forwarded: {
|
||||
errorMessage += message + ".";
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixUpdateUserChatColorError::Unknown:
|
||||
default: {
|
||||
errorMessage += "An unknown error has occurred.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace chatterino::commands
|
15
src/controllers/commands/builtin/twitch/UpdateColor.hpp
Normal file
15
src/controllers/commands/builtin/twitch/UpdateColor.hpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
class QString;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CommandContext;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::commands {
|
||||
|
||||
QString updateUserColor(const CommandContext &ctx);
|
||||
|
||||
} // namespace chatterino::commands
|
|
@ -121,10 +121,7 @@ ImagePtr getEmptyImagePtr();
|
|||
|
||||
class ImageExpirationPool
|
||||
{
|
||||
private:
|
||||
friend class Image;
|
||||
friend class CommandController;
|
||||
|
||||
public:
|
||||
ImageExpirationPool();
|
||||
static ImageExpirationPool &instance();
|
||||
|
||||
|
@ -145,7 +142,6 @@ private:
|
|||
*/
|
||||
void freeAll();
|
||||
|
||||
private:
|
||||
// Timer to periodically run freeOld()
|
||||
QTimer *freeTimer_;
|
||||
std::map<Image *, std::weak_ptr<Image>> allImages_;
|
||||
|
|
Loading…
Reference in a new issue