mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
feat: Allow id: prefix in /ban and /timeout (#4945)
ban example: `/ban id:70948394`, equivalent to `/banid 70948394` timeout example: `/timeout id:70948394 10 xd`
This commit is contained in:
parent
68817fa1a1
commit
fcc5f4b3df
|
@ -7,6 +7,7 @@
|
|||
- Minor: The account switcher is now styled to match your theme. (#4817)
|
||||
- Minor: Add an invisible resize handle to the bottom of frameless user info popups and reply thread popups. (#4795)
|
||||
- Minor: The installer now checks for the VC Runtime version and shows more info when it's outdated. (#4847)
|
||||
- Minor: Allow running `/ban` and `/timeout` on User IDs by using the `id:123` syntax (e.g. `/timeout id:22484632 1m stop winning`). (#4945)
|
||||
- Minor: The `/usercard` command now accepts user ids. (#4934)
|
||||
- Minor: Add menu actions to reply directly to a message or the original thread root. (#4923)
|
||||
- Minor: The `/reply` command now replies to the latest message of the user. (#4919)
|
||||
|
|
|
@ -78,7 +78,42 @@ QString formatBanTimeoutError(const char *operation, HelixBanUserError error,
|
|||
break;
|
||||
}
|
||||
return errorMessage;
|
||||
};
|
||||
}
|
||||
|
||||
void banUserByID(const ChannelPtr &channel, const TwitchChannel *twitchChannel,
|
||||
const QString &sourceUserID, const QString &targetUserID,
|
||||
const QString &reason, const QString &displayName)
|
||||
{
|
||||
getHelix()->banUser(
|
||||
twitchChannel->roomId(), sourceUserID, targetUserID, std::nullopt,
|
||||
reason,
|
||||
[] {
|
||||
// No response for bans, they're emitted over pubsub/IRC instead
|
||||
},
|
||||
[channel, displayName](auto error, auto message) {
|
||||
auto errorMessage =
|
||||
formatBanTimeoutError("ban", error, message, displayName);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
}
|
||||
|
||||
void timeoutUserByID(const ChannelPtr &channel,
|
||||
const TwitchChannel *twitchChannel,
|
||||
const QString &sourceUserID, const QString &targetUserID,
|
||||
int duration, const QString &reason,
|
||||
const QString &displayName)
|
||||
{
|
||||
getHelix()->banUser(
|
||||
twitchChannel->roomId(), sourceUserID, targetUserID, duration, reason,
|
||||
[] {
|
||||
// No response for timeouts, they're emitted over pubsub/IRC instead
|
||||
},
|
||||
[channel, displayName](auto error, auto message) {
|
||||
auto errorMessage =
|
||||
formatBanTimeoutError("timeout", error, message, displayName);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
@ -120,32 +155,41 @@ QString sendBan(const CommandContext &ctx)
|
|||
return "";
|
||||
}
|
||||
|
||||
auto target = words.at(1);
|
||||
stripChannelName(target);
|
||||
|
||||
const auto &rawTarget = words.at(1);
|
||||
auto [targetUserName, targetUserID] = parseUserNameOrID(rawTarget);
|
||||
auto reason = words.mid(2).join(' ');
|
||||
|
||||
getHelix()->getUserByName(
|
||||
target,
|
||||
[channel, currentUser, twitchChannel, target,
|
||||
reason](const auto &targetUser) {
|
||||
getHelix()->banUser(
|
||||
twitchChannel->roomId(), currentUser->getUserId(),
|
||||
targetUser.id, std::nullopt, reason,
|
||||
[] {
|
||||
// No response for bans, they're emitted over pubsub/IRC instead
|
||||
},
|
||||
[channel, target, targetUser](auto error, auto message) {
|
||||
auto errorMessage = formatBanTimeoutError(
|
||||
"ban", error, message, targetUser.displayName);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
},
|
||||
[channel, target] {
|
||||
// Equivalent error from IRC
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("Invalid username: %1").arg(target)));
|
||||
});
|
||||
if (!targetUserID.isEmpty())
|
||||
{
|
||||
banUserByID(channel, twitchChannel, currentUser->getUserId(),
|
||||
targetUserID, reason, targetUserID);
|
||||
getHelix()->banUser(
|
||||
twitchChannel->roomId(), currentUser->getUserId(), targetUserID,
|
||||
std::nullopt, reason,
|
||||
[] {
|
||||
// No response for bans, they're emitted over pubsub/IRC instead
|
||||
},
|
||||
[channel, targetUserID{targetUserID}](auto error, auto message) {
|
||||
auto errorMessage =
|
||||
formatBanTimeoutError("ban", error, message, targetUserID);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
getHelix()->getUserByName(
|
||||
targetUserName,
|
||||
[channel, currentUser, twitchChannel,
|
||||
reason](const auto &targetUser) {
|
||||
banUserByID(channel, twitchChannel, currentUser->getUserId(),
|
||||
targetUser.id, reason, targetUser.displayName);
|
||||
},
|
||||
[channel, targetUserName{targetUserName}] {
|
||||
// Equivalent error from IRC
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("Invalid username: %1").arg(targetUserName)));
|
||||
});
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
@ -188,17 +232,8 @@ QString sendBanById(const CommandContext &ctx)
|
|||
auto target = words.at(1);
|
||||
auto reason = words.mid(2).join(' ');
|
||||
|
||||
getHelix()->banUser(
|
||||
twitchChannel->roomId(), currentUser->getUserId(), target, std::nullopt,
|
||||
reason,
|
||||
[] {
|
||||
// No response for bans, they're emitted over pubsub/IRC instead
|
||||
},
|
||||
[channel, target](auto error, auto message) {
|
||||
auto errorMessage =
|
||||
formatBanTimeoutError("ban", error, message, "#" + target);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
banUserByID(channel, twitchChannel, currentUser->getUserId(), target,
|
||||
reason, target);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
@ -242,8 +277,8 @@ QString sendTimeout(const CommandContext &ctx)
|
|||
return "";
|
||||
}
|
||||
|
||||
auto target = words.at(1);
|
||||
stripChannelName(target);
|
||||
const auto &rawTarget = words.at(1);
|
||||
auto [targetUserName, targetUserID] = parseUserNameOrID(rawTarget);
|
||||
|
||||
int duration = 10 * 60; // 10min
|
||||
if (words.size() >= 3)
|
||||
|
@ -257,27 +292,28 @@ QString sendTimeout(const CommandContext &ctx)
|
|||
}
|
||||
auto reason = words.mid(3).join(' ');
|
||||
|
||||
getHelix()->getUserByName(
|
||||
target,
|
||||
[channel, currentUser, twitchChannel, target, duration,
|
||||
reason](const auto &targetUser) {
|
||||
getHelix()->banUser(
|
||||
twitchChannel->roomId(), currentUser->getUserId(),
|
||||
targetUser.id, duration, reason,
|
||||
[] {
|
||||
// No response for timeouts, they're emitted over pubsub/IRC instead
|
||||
},
|
||||
[channel, target, targetUser](auto error, auto message) {
|
||||
auto errorMessage = formatBanTimeoutError(
|
||||
"timeout", error, message, targetUser.displayName);
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
},
|
||||
[channel, target] {
|
||||
// Equivalent error from IRC
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("Invalid username: %1").arg(target)));
|
||||
});
|
||||
if (!targetUserID.isEmpty())
|
||||
{
|
||||
timeoutUserByID(channel, twitchChannel, currentUser->getUserId(),
|
||||
targetUserID, duration, reason, targetUserID);
|
||||
}
|
||||
else
|
||||
{
|
||||
getHelix()->getUserByName(
|
||||
targetUserName,
|
||||
[channel, currentUser, twitchChannel,
|
||||
targetUserName{targetUserName}, duration,
|
||||
reason](const auto &targetUser) {
|
||||
timeoutUserByID(channel, twitchChannel,
|
||||
currentUser->getUserId(), targetUser.id,
|
||||
duration, reason, targetUser.displayName);
|
||||
},
|
||||
[channel, targetUserName{targetUserName}] {
|
||||
// Equivalent error from IRC
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("Invalid username: %1").arg(targetUserName)));
|
||||
});
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -62,6 +62,33 @@ void stripChannelName(QString &channelName)
|
|||
}
|
||||
}
|
||||
|
||||
std::pair<ParsedUserName, ParsedUserID> parseUserNameOrID(const QString &input)
|
||||
{
|
||||
if (input.startsWith("id:"))
|
||||
{
|
||||
return {
|
||||
{},
|
||||
input.mid(3),
|
||||
};
|
||||
}
|
||||
|
||||
QString userName = input;
|
||||
|
||||
if (userName.startsWith('@') || userName.startsWith('#'))
|
||||
{
|
||||
userName.remove(0, 1);
|
||||
}
|
||||
if (userName.endsWith(','))
|
||||
{
|
||||
userName.chop(1);
|
||||
}
|
||||
|
||||
return {
|
||||
userName,
|
||||
{},
|
||||
};
|
||||
}
|
||||
|
||||
QRegularExpression twitchUserNameRegexp()
|
||||
{
|
||||
static QRegularExpression re(
|
||||
|
|
|
@ -16,6 +16,16 @@ void stripUserName(QString &userName);
|
|||
// stripChannelName removes any @ prefix or , suffix to make it more suitable for command use
|
||||
void stripChannelName(QString &channelName);
|
||||
|
||||
using ParsedUserName = QString;
|
||||
using ParsedUserID = QString;
|
||||
|
||||
/**
|
||||
* Parse the given input into either a user name or a user ID
|
||||
*
|
||||
* User IDs take priority and are parsed if the input starts with `id:`
|
||||
*/
|
||||
std::pair<ParsedUserName, ParsedUserID> parseUserNameOrID(const QString &input);
|
||||
|
||||
// Matches a strict Twitch user login.
|
||||
// May contain lowercase a-z, 0-9, and underscores
|
||||
// Must contain between 1 and 25 characters
|
||||
|
|
|
@ -160,6 +160,116 @@ TEST(UtilTwitch, StripChannelName)
|
|||
}
|
||||
}
|
||||
|
||||
TEST(UtilTwitch, ParseUserNameOrID)
|
||||
{
|
||||
struct TestCase {
|
||||
QString input;
|
||||
QString expectedUserName;
|
||||
QString expectedUserID;
|
||||
};
|
||||
|
||||
std::vector<TestCase> tests{
|
||||
{
|
||||
"pajlada",
|
||||
"pajlada",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"Pajlada",
|
||||
"Pajlada",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"@Pajlada",
|
||||
"Pajlada",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"#Pajlada",
|
||||
"Pajlada",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"#Pajlada,",
|
||||
"Pajlada",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"#Pajlada,",
|
||||
"Pajlada",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"@@Pajlada,",
|
||||
"@Pajlada",
|
||||
{},
|
||||
},
|
||||
{
|
||||
// We only strip one character off the front
|
||||
"#@Pajlada,",
|
||||
"@Pajlada",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"@@Pajlada,,",
|
||||
"@Pajlada,",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"",
|
||||
"",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"@",
|
||||
"",
|
||||
{},
|
||||
},
|
||||
{
|
||||
",",
|
||||
"",
|
||||
{},
|
||||
},
|
||||
{
|
||||
// We purposefully don't handle spaces at the end, as all expected usages of this function split the message up by space and strip the parameters by themselves
|
||||
", ",
|
||||
", ",
|
||||
{},
|
||||
},
|
||||
{
|
||||
// We purposefully don't handle spaces at the start, as all expected usages of this function split the message up by space and strip the parameters by themselves
|
||||
" #",
|
||||
" #",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"id:123",
|
||||
{},
|
||||
"123",
|
||||
},
|
||||
{
|
||||
"id:",
|
||||
{},
|
||||
"",
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto &[input, expectedUserName, expectedUserID] : tests)
|
||||
{
|
||||
auto [actualUserName, actualUserID] = parseUserNameOrID(input);
|
||||
|
||||
EXPECT_EQ(actualUserName, expectedUserName)
|
||||
<< "name " << qUtf8Printable(actualUserName) << " ("
|
||||
<< qUtf8Printable(input) << ") did not match expected value "
|
||||
<< qUtf8Printable(expectedUserName);
|
||||
|
||||
EXPECT_EQ(actualUserID, expectedUserID)
|
||||
<< "id " << qUtf8Printable(actualUserID) << " ("
|
||||
<< qUtf8Printable(input) << ") did not match expected value "
|
||||
<< qUtf8Printable(expectedUserID);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UtilTwitch, UserLoginRegexp)
|
||||
{
|
||||
struct TestCase {
|
||||
|
|
Loading…
Reference in a new issue