Add new command placeholders: {channel.name}, {channel.id}, {stream.game}, {stream.title}, {my.id}, {my.name} (#3155)

Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
Mm2PL 2021-09-11 14:35:26 +02:00 committed by GitHub
parent c0f4a410fa
commit 9b9fd7d403
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 117 additions and 15 deletions

View file

@ -2,6 +2,7 @@
## Unversioned ## Unversioned
- Minor: Add `{channel.name}`, `{channel.id}`, `{stream.game}`, `{stream.title}`, `{my.id}`, `{my.name}` placeholders for commands (#3155)
- Minor: Remove TwitchEmotes.com attribution and the open/copy options when right-clicking a Twitch Emote. (#2214, #3136) - Minor: Remove TwitchEmotes.com attribution and the open/copy options when right-clicking a Twitch Emote. (#2214, #3136)
- Minor: Strip leading @ and trailing , from username in /user and /usercard commands. (#3143) - Minor: Strip leading @ and trailing , from username in /user and /usercard commands. (#3143)
- Minor: Display a system message when reloading subscription emotes to match BTTV/FFZ behavior (#3135) - Minor: Display a system message when reloading subscription emotes to match BTTV/FFZ behavior (#3135)

View file

@ -226,6 +226,72 @@ bool appendWhisperMessageStringLocally(const QString &textNoEmoji)
} }
return false; return false;
} }
const std::map<QString,
std::function<QString(const QString &, const ChannelPtr &)>>
COMMAND_VARS{
{
"channel.name",
[](const auto &altText, const auto &channel) {
(void)(altText); //unused
return channel->getName();
},
},
{
"channel.id",
[](const auto &altText, const auto &channel) {
auto *tc = dynamic_cast<TwitchChannel *>(channel.get());
if (tc == nullptr)
{
return altText;
}
return tc->roomId();
},
},
{
"stream.game",
[](const auto &altText, const auto &channel) {
auto *tc = dynamic_cast<TwitchChannel *>(channel.get());
if (tc == nullptr)
{
return altText;
}
const auto &status = tc->accessStreamStatus();
return status->live ? status->game : altText;
},
},
{
"stream.title",
[](const auto &altText, const auto &channel) {
auto *tc = dynamic_cast<TwitchChannel *>(channel.get());
if (tc == nullptr)
{
return altText;
}
const auto &status = tc->accessStreamStatus();
return status->live ? status->title : altText;
},
},
{
"my.id",
[](const auto &altText, const auto &channel) {
(void)(channel); //unused
auto uid = getApp()->accounts->twitch.getCurrent()->getUserId();
return uid.isEmpty() ? altText : uid;
},
},
{
"my.name",
[](const auto &altText, const auto &channel) {
(void)(channel); //unused
auto name =
getApp()->accounts->twitch.getCurrent()->getUserName();
return name.isEmpty() ? altText : name;
},
},
};
} // namespace } // namespace
namespace chatterino { namespace chatterino {
@ -855,7 +921,7 @@ QString CommandController::execCommand(const QString &textNoEmoji,
if (it != this->userCommands_.end()) if (it != this->userCommands_.end())
{ {
text = getApp()->emotes->emojis.replaceShortCodes( text = getApp()->emotes->emojis.replaceShortCodes(
this->execCustomCommand(words, it.value(), dryRun)); this->execCustomCommand(words, it.value(), dryRun, channel));
words = text.split(' ', QString::SkipEmptyParts); words = text.split(' ', QString::SkipEmptyParts);
@ -887,7 +953,7 @@ QString CommandController::execCommand(const QString &textNoEmoji,
const auto it = this->userCommands_.find(commandName); const auto it = this->userCommands_.find(commandName);
if (it != this->userCommands_.end()) if (it != this->userCommands_.end())
{ {
return this->execCustomCommand(words, it.value(), dryRun); return this->execCustomCommand(words, it.value(), dryRun, channel);
} }
} }
@ -906,11 +972,13 @@ void CommandController::registerCommand(QString commandName,
QString CommandController::execCustomCommand(const QStringList &words, QString CommandController::execCustomCommand(const QStringList &words,
const Command &command, const Command &command,
bool dryRun) bool dryRun, ChannelPtr channel,
std::map<QString, QString> context)
{ {
QString result; QString result;
static QRegularExpression parseCommand("(^|[^{])({{)*{(\\d+\\+?)}"); static QRegularExpression parseCommand(
R"((^|[^{])({{)*{(\d+\+?|([a-zA-Z.-]+)(?:;(.+?))?)})");
int lastCaptureEnd = 0; int lastCaptureEnd = 0;
@ -941,8 +1009,28 @@ QString CommandController::execCustomCommand(const QStringList &words,
bool ok; bool ok;
int wordIndex = wordIndexMatch.replace("=", "").toInt(&ok); int wordIndex = wordIndexMatch.replace("=", "").toInt(&ok);
if (!ok || wordIndex == 0) if (!ok || wordIndex == 0)
{
auto varName = match.captured(4);
auto altText = match.captured(5); // alt text or empty string
auto var = COMMAND_VARS.find(varName);
if (var != COMMAND_VARS.end())
{
result += var->second(altText, channel);
}
else
{
auto it = context.find(varName);
if (it != context.end())
{
result += it->second.isEmpty() ? altText : it->second;
}
else
{ {
result += "{" + match.captured(3) + "}"; result += "{" + match.captured(3) + "}";
}
}
continue; continue;
} }

View file

@ -34,6 +34,10 @@ public:
CommandModel *createModel(QObject *parent); CommandModel *createModel(QObject *parent);
QString execCustomCommand(const QStringList &words, const Command &command,
bool dryRun, ChannelPtr channel,
std::map<QString, QString> context = {});
private: private:
void load(Paths &paths); void load(Paths &paths);
@ -57,9 +61,6 @@ private:
std::unique_ptr<pajlada::Settings::Setting<std::vector<Command>>> std::unique_ptr<pajlada::Settings::Setting<std::vector<Command>>>
commandsSetting_; commandsSetting_;
QString execCustomCommand(const QStringList &words, const Command &command,
bool dryRun);
QStringList commandAutoCompletions_; QStringList commandAutoCompletions_;
}; };

View file

@ -2101,12 +2101,24 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link,
} }
} }
value.replace("{user}", layout->getMessage()->loginName) value = getApp()->commands->execCustomCommand(
.replace("{channel}", this->channel_->getName()) QStringList(), Command{"(modaction)", value}, true, channel,
.replace("{msg-id}", layout->getMessage()->id) {
.replace("{message}", layout->getMessage()->messageText); {"user.name", layout->getMessage()->loginName},
{"msg.id", layout->getMessage()->id},
{"msg.text", layout->getMessage()->messageText},
// old placeholders
{"user", layout->getMessage()->loginName},
{"msg-id", layout->getMessage()->id},
{"message", layout->getMessage()->messageText},
// new version of this is inside execCustomCommand
{"channel", this->channel()->getName()},
});
value = getApp()->commands->execCommand(value, channel, false); value = getApp()->commands->execCommand(value, channel, false);
channel->sendMessage(value); channel->sendMessage(value);
} }
break; break;

View file

@ -157,8 +157,8 @@ ModerationPage::ModerationPage()
// clang-format off // clang-format off
auto label = modMode.emplace<QLabel>( auto label = modMode.emplace<QLabel>(
"Moderation mode is enabled by clicking <img width='18' height='18' src=':/buttons/modModeDisabled.png'> in a channel that you moderate.<br><br>" "Moderation mode is enabled by clicking <img width='18' height='18' src=':/buttons/modModeDisabled.png'> in a channel that you moderate.<br><br>"
"Moderation buttons can be bound to chat commands such as \"/ban {user}\", \"/timeout {user} 1000\", \"/w someusername !report {user} was bad in channel {channel}\" or any other custom text commands.<br>" "Moderation buttons can be bound to chat commands such as \"/ban {user.name}\", \"/timeout {user.name} 1000\", \"/w someusername !report {user.name} was bad in channel {channel.name}\" or any other custom text commands.<br>"
"For deleting messages use /delete {msg-id}.<br><br>" "For deleting messages use /delete {msg.id}.<br><br>"
"More information can be found <a href='https://wiki.chatterino.com/Moderation/#moderation-mode'>here</a>."); "More information can be found <a href='https://wiki.chatterino.com/Moderation/#moderation-mode'>here</a>.");
label->setOpenExternalLinks(true); label->setOpenExternalLinks(true);
label->setWordWrap(true); label->setWordWrap(true);
@ -188,7 +188,7 @@ ModerationPage::ModerationPage()
view->addButtonPressed.connect([] { view->addButtonPressed.connect([] {
getSettings()->moderationActions.append( getSettings()->moderationActions.append(
ModerationAction("/timeout {user} 300")); ModerationAction("/timeout {user.name} 300"));
}); });
} }