mirror of
https://github.com/perlbot/perlbuut
synced 2025-06-08 15:45:42 -04:00
Upload some docs to the wiki
parent
13261e2634
commit
1db4555c3a
6 changed files with 274 additions and 1 deletions
11
pluginmanager.md
Normal file
11
pluginmanager.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
Basic specification:
|
||||
|
||||
Read plugins.
|
||||
Fork children.
|
||||
Monitor children.
|
||||
Wait for death.
|
||||
Fork new child.
|
||||
(Note, children required to die on schedule?)
|
||||
Pass lines out to children, deal with output, pass output to main bot object.
|
||||
Pre-fork model similar to apache. Plugin objects initialized after fork. Must not assume persistent state.
|
||||
|
57
plugins.md
Normal file
57
plugins.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
A plugin is implemented as a single file in the plugin directory. This is of course named 'plugins' unless otherwise specified in the bot's config file. Each file is executed and the return value (of the file!) is examined. If the return value is a subroutine, then it is taken as a basic plugin that executes in the 'main' phase of plugin handling and has no special configuration. If, on the other hand, the plugin returns a string, this is taken as the name of a package to call the method 'new' on to get an object. This object is then initialized and so forth then called with input in the normal course of plugin handling. Note that objects are still subject to the configuration specified in the plugin.conf file, but however, can do more configuration of how the main bot object interacts with the plugin, specifically when in the plugin loop they're activated and perhaps even a chance to preprocess or postprocess the output of other plugins. This is of course the 'advanced' method. Someday I'll actually document what the interface for all of this actually is.
|
||||
|
||||
File: echo.pm
|
||||
|
||||
#----------------START--------------
|
||||
sub {
|
||||
my( $said ) = @_;
|
||||
|
||||
print "You said: $said->{body}";
|
||||
}
|
||||
#-----------------EOF---------------
|
||||
|
||||
File: morecomplicated.pm
|
||||
|
||||
#----------------START--------------
|
||||
package Bot::BB3::Plugin::Complicated;
|
||||
|
||||
sub new {
|
||||
my( $class ) = @_;
|
||||
|
||||
return bless {}, $class;
|
||||
}
|
||||
|
||||
sub initialize {
|
||||
my( $self ) = @_;
|
||||
|
||||
#stuff
|
||||
}
|
||||
|
||||
#Class name to execute
|
||||
"Bot::BB3::Plugin::Complicated";
|
||||
#-----------------EOF---------------
|
||||
|
||||
In particular note is the string returned, this is the name of the package to invoke. Note that you could use this as a dummy file to invoke modules installed elsewhere on the system, for example:
|
||||
|
||||
File: dummymodule.pm
|
||||
|
||||
#----------------START--------------
|
||||
use MyModule::Somewhere;
|
||||
|
||||
"MyModule::Somewhere";
|
||||
#-----------------EOF---------------
|
||||
|
||||
This simply calls 'use' to load the module and then returns the name.
|
||||
|
||||
Note that plugins who return a coderef are 'wrapped' by invoking Bot::BB3::PluginWrapper->new( $name, $coderef );
|
||||
This provides the basic example for implementing a plugin object.
|
||||
|
||||
More Notes:
|
||||
Plugins can check if the line has been handled before them.
|
||||
Plugins activate only if configured to do so.
|
||||
Plugins handle parsing..
|
||||
Plugins need constants to return in certain situations.
|
||||
Plugins have options to not handle the line, thus needing a NOT_HANDLED; constant?
|
||||
Handled is the default I think?
|
||||
Every plugin would have to return a constant.. maybe.
|
||||
|
68
quick_start.md
Normal file
68
quick_start.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
The Quick Start Guide.
|
||||
|
||||
From a user's perspective, there are three main directories you should be interested in. These are etc/, bin/ and plugins/. More detailed explanations follow:
|
||||
|
||||
etc/ is the directory that contains all of the files necessary to configure the bot. At the moment it should only contain two files, bb3.conf and plugins.conf.
|
||||
|
||||
plugins.conf use a simple configuration language to control which plugins will respond in what channel and to what 'authorization' level. At the moment the syntax for the configuration language looks something like:
|
||||
server "*" {
|
||||
channel "#test" {
|
||||
plugin "join" { access: op; addressed: true }
|
||||
}
|
||||
}
|
||||
|
||||
Essentially it's a series of nested filters that apply to the final set of conditions. The first filter is the "server" command, which attempts to match against the server name of whatever irc network generated the message the bot is attempting to reply to. The second filter is "channel" which obviously attempts to match against the channel the message was received on. Finally the "plugin" filter, again, matches against the specific plugin that it's trying to activate.
|
||||
|
||||
Note that you can use the '*' wildcard anywhere you could specify a specific string to cause that filter to always match. Also note you can have multiples of any section of the above example, that is, you can have multiple server blocks in the file, you can have multiple channel blocks inside a server block and so on. The configuration options are generally merged in an additive file, so if you have multiple blocks that have different conditions for the same plugin on the same channel on the same server, all of these conditions will have to be matched before it executes.
|
||||
|
||||
The final section of the above example is the actual "plugin" section, which again, tries to match against a specific plugin name and then applies the options listed inside the curly braces. In this case the two options being applied are 'access' and 'addressed'. The 'addressed' option controls whether or not the bot needs to have been addressed for this plugin to activate, in this case a value of true means it has to have been addressed, in the form of 'buubot: echo foo'. You can also specify 'false' to do the opposite, this is also useful for using a general filter to cause every plugin to be addressed and then setting specific ones to 'false' to let them respond without being addressed
|
||||
|
||||
The other option in the example is 'access' which simply controls what level of "authorization" might be required to activate the plugin. As of now there are only three levels of access, the default one which is what happens when you don't specify an access option, means anyone can use it. The second is 'op' which means you must currently be 'opped' in the channel where you're attempting to use the command. The last acceptable value is 'root' which means your hostmask much match the root_mask value in the etc/bb3.conf.
|
||||
|
||||
|
||||
The second file in the etc/ directory is the bb3.conf file. Note that the bin/bb3 launching command automatically looks for a file named etc/bb3.conf to configure itself.
|
||||
|
||||
Obviously, this file contains directives used to configure which networks the bot connects to and under what name, along with root user, people to ignore, and various other plugin or role specific configuration directives. Technically this file uses syntax from the perl module Config::General, but you can think of it as the same language that Apache uses in its conf file, which can be summarized as <Foo bar> option_value 42 </Foo> where <Foo> starts a section named 'bar' and option_value 42 sets the option_value to 42, whatever thay may do.
|
||||
|
||||
The main configuration directive is named 'bot' and looks something like:
|
||||
|
||||
<bot buubot>
|
||||
channel \#buubot
|
||||
channel \#bottest
|
||||
|
||||
ignore avarbot
|
||||
ignore otherbot
|
||||
|
||||
server irc.freenode.org
|
||||
root_mask user@host.org
|
||||
</bot>
|
||||
|
||||
|
||||
The first section is the <bot buubot> part, which starts a new 'bot' section, which is ended by the corresponding </bot> tag, and specifies that the nickname of this bot, when it connects to the irc server, should be 'buubot'. Because that's an awesome name.
|
||||
|
||||
The second section is the 'channel' directive which specifies a list of channels to join as soon as the bot has connected. Each channel directive specifies a single channel, simply repeat it to join multiple. You can specify as many as the irc network will let you connect to. Note that you have to escape the # (\#) signs in the channel name, since this configuration language uses # to begin a comment.
|
||||
|
||||
The next section is the 'ignores', these simply take the name of an irc user for the bot to ignore, note that the bot will drop ALL input from any user whose nick matches the name specified. Again, you can specify multiple people to ignore simply by specifying the directive multiple times.
|
||||
|
||||
The next directive is really the only required one, and that is the 'server' command, which takes a single argument, the name of an irc server to attempt to connect to. In this case, irc.freenode.org, because I like freenode. Note that unlike the rest of the directives, you can only specify one 'server' directive. If you want to connect to multiple servers at the same time, simply create multiple <bot foo></bot> blocks, each one containing a different server directive.
|
||||
|
||||
The last directive is 'root_mask', which determines which users are able to use plugins with an 'access' set to 'root'. It matches fairly directly against your IRC hostmask, to set it to something similar.
|
||||
|
||||
There are a number of other configuration directives in the file, which mostly aren't that interesting, with the possible exception of the following:
|
||||
|
||||
<plugin_manager>
|
||||
default_plugin factoids
|
||||
</plugin_manager>
|
||||
|
||||
This block controls which plugin responds when the bot is addressed and it can't find a plugin that matches what the user said.
|
||||
|
||||
An example, if our bot was currently named 'buubot' and a user spoke to it as such: "buubot: echo foo bar", assuming you left the default plugin named 'echo' in the directory, buubot would match the 'echo' at the start of the string and then pass the rest of the arguments to the echo plugin. However, if a user spoke to the bot like "buubot: flibble", and assuming you didn't have a plugin named 'flibble' in your plugin directory, then it would activate the plugin specified as the 'default_plugin' in the above configuration section and pass that string to it. In this case, the default_plugin is configured to be 'factoids' which is the plugin that handles learning random strings based on user input, try 'help factoids' once the bot is running for more information.
|
||||
|
||||
You could set this default plugin to be any of the plugin in the plugins/directive, for example you might prefer to set it to the 'eval' plugin or anything else, if you prefer the bot to default to that.
|
||||
|
||||
|
||||
Anyway, once you've configured the above files to your liking, you can actually launch the bot with the command bin/bb3. This launches the bot and attempts to get it connected to the irc networks you specified. Note that you can pass the flag '-d' to bin/bb3 to have it attempt to 'daemonize' itself and stop spewing output to your console. Also note that it really expects to be run from the base directory you checked out or downloaded, that is, you should have a folder named something containing bin, etc, plugins, lib and so forth, and you really should run this command from inside that directory with the command 'bin/bb3'.
|
||||
|
||||
The second interesting command in the bin directory is the 'evalserver' command which attempts to launch the evalserver. As a quick summary, the evalserver runs as a standalone tcp-socket based server which receives commands to evaluate, evaluates them and returns the output. By default it only listens to localhost connections on the port 14400, unfortunately this port can't be configured at the moment, if you want to change it just edit lib/EvalServer.pm and search for it.
|
||||
|
||||
Note that this server must be run as root, so it can perform the necessary security based actions of invoking 'chroot' and dropping its user and group id to the user 'nobody'. You're welcome to think this is moderately unsafe, but the code involved is relatively simple and easy to audit. The two files involved are lib/EvalServer.pm and lib/eval.pl, which performs the actual security based sections. In any case, unless you either find an actual hole or can come up with an alternate way to perform this, please refrain from commenting. I'll note that I've been running this server, as root, for over a year now on highly populated IRC channels, and have received no ill-effects.
|
38
said_object.md
Normal file
38
said_object.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
The said object is the heart of the bb3 communication system. Specifically it's the only argument passed to plugins of various types. It contains all of the knowledge necessary to respond to a single IRC line (or other forms of communication). The values are as follows:
|
||||
|
||||
$said = {
|
||||
body_raw => The exact text that it was sent
|
||||
body => The potentially parsed results of the body. Note that several things modify
|
||||
it at the moment. For example, the name of the bot is removed if it is
|
||||
found at the start. In some cases, if the leading text matches the name
|
||||
of a command, it is also removed.
|
||||
recommended_args => An array ref of words in the text.
|
||||
Note that this only exists for 'command' type plugins.
|
||||
It's just a guess to save some time, it's split on whitespace
|
||||
or similar.
|
||||
channel => The channel that it was seen in. In the case of IRC this is of the form
|
||||
#channel, but other roles use channel names starting with *, for example,
|
||||
*irc_msg or *dcc_send or even *web for the web based interface.
|
||||
addressed => Whether or not the bot detected its name at the beginning
|
||||
sender_raw => The raw description of the person who sent the text`
|
||||
name => The irc nick of the person who sent the line
|
||||
ircname => The irc username of the sender.
|
||||
host => The host of the person who sent the text
|
||||
server => The server it was seen on
|
||||
pci_id => The POE::Component::IRC ID, obviously internal use only.
|
||||
my_name => The current name of the bot who saw it.
|
||||
special_commands => This is an arrayref of arrayrefs, elements that are used as
|
||||
arguments to $poe_component_irc->yield, if they start with pci_
|
||||
that is: special_commands => [ [ 'pci_join', '#foo', '#bar' ] ]; causes:
|
||||
$pci->yield( 'join', => '#foo', '#bar' );
|
||||
If the command starts with pm_ it calls the specified method on the
|
||||
$plugin_manager object that executed this plugin. If it starts with
|
||||
bb3_ it calls the method on the parent bb3 object that spawned the
|
||||
plugin_manager in the first place.
|
||||
parent_session => This is added by the plugin_manager to record which session sent
|
||||
us the $said event in the first place. Mostly internal use.
|
||||
by_root => Set to 1 if the line was "spoken" by someone who matches the root_mask
|
||||
by_chan_op => Set to 1 if the line was "spoken" by someone who currently has
|
||||
operator status in the channel he spoke the line in.
|
||||
};
|
||||
|
1
ttest.md
1
ttest.md
|
@ -1 +0,0 @@
|
|||
Welcome to the perlbuut wiki!
|
100
writing_plugins.md
Normal file
100
writing_plugins.md
Normal file
|
@ -0,0 +1,100 @@
|
|||
Plugins come in two forms. The first form is a simple subroutine ref. The second is a complex object.
|
||||
In the first case, simply define an annonymous subroutine reference in a file in the appropriate directory (typically the one named 'plugins') with the file name being the name of the command you want. You can do other things other than define an annonymous sub, but make sure that the subref is the last value to be returned. This subroutine ref is encapsulated as part of a larger object and then executed whenever the bot sees a string that matches the name of the file, which is also the command name. The subroutine is passed two arguments, the first is $said, which is documented in docs/said, and the second is the $plugin_manager object, which isn't really documented.
|
||||
|
||||
As mentioned, $said contains all of the necessary information for you to respond to the user who triggered your plugin. The most relevant fields of $said are as follows:
|
||||
body: This is the text sent to the bot. Note that if it was addressed to the bot, by prefixing the sentence with the bot's current name, this is stripped. The command name, which caused this particular plugin to be triggered, is also stripped. That is, if the bot was named 'bb3' and the example command was named 'echo', then the string "bb3: echo: hello world" would generate a body field of "hello world".
|
||||
raw_body: The exact text sent to the bot, includes bot name, command name, and so on.
|
||||
recommended_args: This is an array ref of whitespace split text that is supposed to be a decent guess as to how you should interpret the text sent to you. "echo foo bar" would generate a recommended args of ['foo','bar'].
|
||||
See docs/said for more complete documentation of the $said 'object'.
|
||||
|
||||
Simple plugins can use print to communicate back to the caller. They should also be able to return a value from the end of the subroutine that will be displayed to the user.
|
||||
|
||||
Plugins also use the __DATA__ handle to store documentation for themselves.
|
||||
|
||||
Example, echo.pm:
|
||||
sub {
|
||||
my( $said, $pm ) = @_;
|
||||
|
||||
print $said->{body};
|
||||
}
|
||||
__DATA__
|
||||
Example echo plugin. Outputs whatever it sees.
|
||||
|
||||
--- END OF EXAMPLE ---
|
||||
|
||||
There are a couple of key points. The first is the file name, in this case, 'echo.pm'. This filename determines the command name the plugin will by activated against. The .pm suffix is also important since the bot will ignore files that don't end in .pm.
|
||||
The second is the arguments passed to the sub. All simple plugins take two arguments, $said and $pm. $said is the object mentioned above. $pm is the Bot::BB3::PluginManager object, which is the internal object that handles loading and dispatching to the various plugins. It's mostly useful for accessing configuration information about the bot or for getting references to other plugins. 99% of plugins will never need to use this object. For a moderately simple example of using this object to access other plugins, see plugins/help.pm.
|
||||
The next point is the 'print' and the and $said->{body}. The routine that activates this plugin used a tied STDOUT to allow 'print' to be used for outputting text, so you can't get too clever. $said->{body} is, as mentioned frequently, the remainder of the text that caused the bot to activate this plugin, minus the bot name and the command name.
|
||||
The last bit, the __DATA__ section, is of course the documentation for the plugin and is read by plugins/help.pm and possibly other areas in order to provide helpful information to the user.
|
||||
|
||||
|
||||
|
||||
Complicated plugins, or at least, not simple plugins, allow for much more flexibility in how you define and interact with events from the bot. They're also the only way to respond to every line the bot reads without having to be activated by a specific command string.
|
||||
These types of plugins need to declare a module/package and return a string that contains the name of the package that the PluginManager will invoke new on to get a plugin object.
|
||||
The only two required methods for a 'complex' plugin are 'new' and 'command'. new is invoked by the PluginManager as it loads all of the plugins it finds in the plugins/ directory and should return an object. For a variety of mostly silly reasons, at the moment the object has to be of the type 'blessed hash' and contain the following key/value pairs in the hash. If you don't like this, send a patch.
|
||||
The blessed hash should contain a 'name' field, containing the name that the plugin should be activated by, as well as the 'opts' key, which points to a hashref containing option=>value pairs. There are four supported options that may be present in the 'opts' hash. They are, handler, command, preprocess and postprocess. One or more of these has to be set or the plugin will never be activated. The exact meanings are as follows:
|
||||
|
||||
command => 1
|
||||
This causes the bot to activate your plugin as a simple command, which is incidentally how the above documented 'simple plugins' are implemented. In short it looks for the name of your plugin at the beginning of the string followed by some optional delimiter characters (such as : or ,) and then calls your plugin's ->command method.
|
||||
handler => 1
|
||||
This causes the bot to activate your plugin on every line that the bot sees. It calls the method ->handle.
|
||||
preprocess => 1
|
||||
This causes your plugin to be invoked on every line the bot sees, before any other plugin is activated. Note that you are simply passed a $said object, but this is the same object that every other plugin will see, so you can modify it at will.
|
||||
postprocess => 1
|
||||
Much the same as preprocess, this causes your plugin to be invoked after every other plugin has finished. You are passed $said and a reference to the output of all of the previous plugins that you may modify as you wish.
|
||||
|
||||
The command method. This is the method that actually does all the work for your plugin and is invoked by the PluginManager depending on the criteria defined above in the opts hash. It is passed: $self, $said, $pm. $self and $said are your object and the said object obviously and $pm is the PluginManager object as mentioned above. Note that 'handle' options invoke the 'handle' method instead.
|
||||
This method is expected to return a list of two items. Note that this is a list and not an array reference. This first item should be a string, either 'handled' or an empty string ''. The second item is another string containing the text you wish to output to the user.
|
||||
Digression: Plugin Chains. When a line is seen by the bot, it creates a plugin chain, that is, a list of all the plugins the bot thinks might be activated for that line, such as commands that match or handlers that always activate and so on. However, in an attempt to ensure that only a single plugin is actually activated per line, each plugin can return either 'handled' or '' as the first item it returns. If the string is 'handled' then the plugin chain is aborted at that point and the output as of that point is the final output from the bot for that line. Note that you can return an empty string as the first item, thus saying you haven't actually handled the line, but still output text by returning it as the second item. This text will be prepended to whatever else is output by other plugins down the line of the chain. Note that all 'simple plugins' always return 'handled' when they're called. Note that 'handler' plugins can't return handled, their output is always concatenated onwards.
|
||||
|
||||
Example, FlibbleEcho.pm:
|
||||
|
||||
package Bot::BB3::Plugin::FlibbleEcho;
|
||||
|
||||
sub new {
|
||||
my( $class ) = @_;
|
||||
my $self = bless {}, $class;
|
||||
$self->{name} = "echo";
|
||||
$self->{opts} = { command => 1 };
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub postload {
|
||||
my( $self, $pm ) = @_;
|
||||
|
||||
Create_Database();
|
||||
}
|
||||
|
||||
sub initialize {
|
||||
my( $self, $pm, $cache ) = @_;
|
||||
|
||||
$self->{cache} = $cache;
|
||||
# Create database handle maybe
|
||||
}
|
||||
|
||||
sub command {
|
||||
my( $self, $said, $pm ) = @_;
|
||||
|
||||
return( 'handled', "Flibble! $said->{body}" );
|
||||
}
|
||||
|
||||
"Bot::BB3::Plugin::FlibbleEcho";
|
||||
__DATA__
|
||||
The flibble echo. Prepends flibble to whatever string it sees and returns the string! Responds to echo.
|
||||
|
||||
-------- End of Example ----------
|
||||
|
||||
Again, there are some interesting points. The first is the package declaration, this is necessary for us to create our perl object later. Note we stick it inside the Bot::BB3::Plugin namespace, this isn't a requirement, but it at least makes a pretense at organization.
|
||||
The next is the new method where we create the object. The two interesting sub points are the 'name' key which specifies our name and the 'opts' hash which specifies that we should be treated as a 'command', which if you will recall, causes our plugin to be activated when our 'name' is detected at the beginning of a string. Note that despite the fact that the file name is 'FlibbleEcho' we declare our name to be 'echo' and are activated based on that declared name. That is to say, in the case of 'complex plugins', the file name is irrelevant.
|
||||
At the end obviously we return our self as a standard object creation method.
|
||||
|
||||
The postload method is called directly after the plugin is instantiated by the pluginmanager and before the pluginmanager forks off its children. It is useful for performing any 'setup' operations you need done before the plugin is in actual use by the bot.
|
||||
|
||||
The initialize method is called on every plugin loaded directly after the pluginmanager forks its children, so it will be called on each plugin for ever child spawned. It varies from the rest of the methods by being passed a $cache object which conforms to the Cache interface. Note that every plugin gets the same cache object, even across forks. This is useful for IPC and storing temporary values. For example, the 'more' plugin is implemented by saving the data in this cache.
|
||||
|
||||
The command subroutine is very simple in this example, we simply assign our arguments to local variables and then return. We return a string of 'handled' to denote the fact that we've successfully responded to this command and then the text we want to output, which in this case is the string Flibble! prepended to whatever text the user has written.
|
||||
|
||||
At the end of the plugin we return a string, which should be the name of the package we declared at the beginning. This is the package name that the PluginManager will call new on to create this object for this plugin. Note that you could actually simply 'use' another package and then return the name of that package, instead of actually implementing the entire plugin in side this file.
|
||||
|
||||
Again at the very end is the __DATA__ section which contains our help text.
|
Loading…
Add table
Reference in a new issue