-
Notifications
You must be signed in to change notification settings - Fork 3
Plugincrafting: commands
Plugin behaviour, or rather the behaviour of the annotated functions that make up your plugin, can easily be extended using User Defined Attributes. As of time of writing there are a few other UDAs with which you can limit when your functions fire.
// Nested in IRCEventHandler!
struct Command
{
private import kameloso.traits : UnderscoreOpDispatcher;
PrefixPolicy _policy;
string _word;
string _description;
string[] _syntaxes;
bool _hidden;
mixin UnderscoreOpDispatcher;
}
The template mixin UnderscoreOpDispatcher
mixes in an opDispatch
that simulates mutator (setter) functions for all members in that struct or class that start with an underscore, with the underscore omitted as the name of the function.
struct Command
{
PrefixPolicy _policy;
string _word;
string _description;
string[] _syntaxes;
bool _hidden;
mixin UnderscoreOpDispatcher;
// opDispatch then simulates the following:
ref auto policy(PrefixPolicy);
ref auto word(string);
ref auto description(string);
ref auto syntaxes(string);
ref auto hidden(bool);
}
A bot command is the start of a message, delimited with (hardcoded) spaces, colons, exclamation and/or question marks. word
should be set with the string of the prefix you want to catch. It filters messages by what they start with.
// Nested in IRCEventHandler!
struct Regex
{
private import kameloso.traits : UnderscoreOpDispatcher;
PrefixPolicy _policy;
StdRegex!char _engine;
string _expression;
string _description;
bool _hidden;
ref auto expression(string); // explicit function, not generated by UnderscoreOpDispatcher
mixin UnderscoreOpDispatcher;
ref auto policy(PrefixPolicy);
ref auto engine(StdRegex!char);
//ref auto expression(string);
ref auto description(string);
ref auto hidden(bool);
}
A bot regex is like a bot command, except it takes a regular expression string and is applied to the whole message.
PrefixPolicy
decides how a string must start for it to be considered a command. It has five settings;
-
direct
means there is no special start required, "hello" will match "hello". -
prefixed
means the command should start with the command prefix, as configured in the configuration file under[Core]
. The default is!
, allowing for commands like!hello
. -
nickname
means it requires the bot nickname as prefix (kameloso: hello
) and the function will not trigger without it -- except when targetted directly to the bot inQUERY
events, where it is optional.
As such we can annotate a function that requires a string to be prefixed with the bot nickname, plus a command to control behaviour.
@(IRCEventHandler()
.onEvent(IRCEvent.Type.CHAN)
.addCommand(
IRCEventHandler.Command()
.word("hello")
.policy(PrefixPolicy.nickname)
)
)
void onHello(MyPlugin plugin, const IRCEvent event)
{
import std.stdio : writeln;
writeln("Hello world!");
}
The above would be triggered by saying kameloso: hello
.
@(IRCEventHandler()
.onEvent(IRCEvent.Type.CHAN)
.addCommand(
IRCEventHandler.Command()
.word("hello")
.policy(PrefixPolicy.prefixed)
)
)
void onHello(MyPlugin plugin, const IRCEvent event)
{
import std.stdio : writeln;
writeln("Hello world!");
}
With PrefixPolicy.prefixed
the above would be triggered by saying !hello
, assuming !
is set up to be the command prefix.
@(IRCEventHandler()
.onEvent(IRCEvent.Type.CHAN)
.addRegex(
IRCEventHandler.Regex()
.expression("\bhello\b")
.policy(PrefixPolicy.direct)
)
)
void onHello(MyPlugin plugin, const IRCEvent event)
{
import std.stdio : writeln;
writeln("Hello world!");
}
Expressed as a regular expression, the above would be triggered if a message contained the word hello
.
.expression("\bhello\b")
See the D Wiki page on Regular Expressions as well as the Phobos page on std.regex for more information about crafting expressions.
.permissionsRequired
filters whether a user can access a function based on who they are in the context of the current channel. If the bot doesn't know, it will query the server for information and try to catch the account name.
-
admin
, or you; the bot's administrator(s). -
staff
, or the owners of a channel. -
operator
, or someone in the operator list for the current channel. -
elevated
, or someone with sub-operator elevated privileges in your channel. -
whitelist
, or someone in the whitelist class list for the current channel. -
registered
, or anyone logged onto services (where possible). -
anyone
, or simply everyone, but will still do account lookups. -
ignore
, which will skip all checks.
Much like with Command
s and Regex
es but with sender being filtered, you can control who gets to trigger your functions.
@(IRCEventHandler()
.onEvent(IRCEvent.Type.CHAN)
.permissionsRequired(Permissions.whitelist) // <--
.addCommand(
IRCEventHandler.Command()
.word("hello")
.policy(PrefixPolicy.prefixed)
)
)
void onHello(MyPlugin plugin, const IRCEvent event)
{
import std.stdio : writeln;
writeln("Hello world!");
}
The function will now trigger if someone says kameloso: hello
, iff they are in your whitelist
for the current channel. It will also trigger if you say it, in the admin role, but never if someone outside does. The prefixing kameloso:
will be optional if sent in a query.
By default events will only be able to trigger your functions if they occur in a channel in your homes
array, where applicable. Naturally this doesn't concern event types that don't have a channel, such as a reply to WHOIS
, sent directly to you from the server.
You can annotate functions to work on any and all channels with .channelPolicy
.
@(IRCEvent.Type.JOIN)
@(ChannelPolicy.home)
void onJoin(MyPlugin plugin, const IRCEvent event) { /* ... */ }
This function will only trigger on JOIN
s to your home channels, and is the default behaviour if you don't specify a ChannelPolicy
.
@(IRCEventHandler()
.onEvent(IRCEvent.Type.JOIN)
.channelPolicy(ChannelPolicy.all) // <--
.addCommand(
IRCEventHandler.Command()
.word("hello")
.policy(PrefixPolicy.prefixed)
)
)
void onJoin(MyPlugin plugin, const IRCEvent event) { /* ... */ }
This is the same function, but now it triggers on JOIN
s in any channel your bot is in, even non-homes.
In plugins/admin.d
:
@(IRCEventHandler()
.onEvent(IRCEvent.Type.CHAN)
.onEvent(IRCEvent.Type.QUERY)
.onEvent(IRCEvent.Type.SELFCHAN)
.permissionsRequired(Permissions.admin)
.channelPolicy(ChannelPolicy.home)
.addCommand(
IRCEventHandler.Command()
.word("sudo")
.policy(PrefixPolicy.nickname)
.description("[debug] Sends supplied text to the server, verbatim.")
.syntax("$command [raw string]")
)
)
void onCommandSudo(MyPlugin plugin, const IRCEvent event) { /* ... */ }
This triggers on CHAN
and QUERY
(and SELFCHAN
) events, only when sent by an admin
, with a nickname
bot prefix (kameloso:
) only in CHAN
events, and with the command string sudo
.
In plugins/chatbot.d
:
@(IRCEventHandler()
.onEvent(IRCEvent.Type.CHAN)
.onEvent(IRCEvent.Type.QUERY)
.onEvent(IRCEvent.Type.SELFCHAN)
.permissionsRequired(Permissions.anyone)
.channelPolicy(ChannelPolicy.home)
.addCommand(
IRCEventHandler.Command()
.word("8ball")
.policy(PrefixPolicy.prefixed)
.description("Implements 8ball. Randomises a vague yes/no response.")
)
)
void onCommand8ball(MyPlugin plugin, const IRCEvent event) { /* ... */ }
This triggers only on CHAN
events with any permissions level, with a prefixed
command prefix and the command string 8ball
.
In plugins/webtitles.d
, and note no BotCommand
:
@(IRCEventHandler()
.onEvent(IRCEvent.Type.CHAN)
.onEvent(IRCEvent.Type.SELFCHAN)
.permissionsRequired(Permissions.ignore)
.channelPolicy(ChannelPolicy.home)
)
void onMessage(MyPlugin plugin, const IRCEvent event) { /* ... */ }
This triggers on CHAN
messages sent by anyone, with no command. This means it gets triggered on every message sent by that permissions level, with no filtering.
In plugins/seen.d
:
@(IRCEventHandler()
.onEvent(IRCEvent.Type.RPL_NAMREPLY)
.channelPolicy(ChannelPolicy.any)
)
void onNameReply(MyPlugin plugin, const IRCEvent event) { /* ... */ }
This triggers on RPL_NAMREPLY
, an event that fires when you join a channel and lists everyone in it. It will be called when this happens for any and all channels the bot is in, even if it is not a home, due to ChannelPolicy.any
. There is no command involved.