Plugin development: Creating plugins — Plugin API 1 — Plugin API 2 Main development: Core
Plugins: calc — papertrading — trivia
Pick a name for your plugin, then do the following, in order. Anyone with common sense will be able to print "Hello, World!".
GO GO GO
All plugin coding will require use of forkb0t's development environment, which mainly consists of a core plugin called forkdev. forkdev is a kind of revision control system, coupled with runtime recompilation and debugging tools.
!forkdev new <plugin>
Creates a new plugin.
!forkdev commit <plugin>
Saves the code in the working directory as a revision.
!forkdev version <plugin> [version]
When called without version, displays the currently set version of code forkb0t is running.
If version is given, sets the version forkb0t will use. Version can be a revision number or the word "latest" (the default setting).
!forkdev enable <plugin>
Enables the specified plugin code.
!forkdev disable <plugin>
Disables the specified plugin code.
!forkdev up <plugin>
Short for running commit, version latest, enable, replug.
!forkdev info <plugin>
Returns basic information about a plugin.
!forkdev paste <plugin>
Posts the current Python source for <plugin> to a pastebin site.
!forkdev check <plugin>
Checks the specified plugin for problems.
!forkdev replug [plugin]
Reloads code for specified plugin.
If no plugin specified, reloads all plugins.
NOTE: Replacement for old !replug, but NOT directly compatable.
NOTE: Currently implemented in core code, not in plugin code
!forkdev login
Displays information required to access the online code share
!forkdev getprivate <plugin> <key>
Returns the value of the specified key in the plugin's private store
!forkdev setprivate <plugin> <key> is <value>
Sets the value of the specified key in the plugin's private store
forkb0t development is done online in a shared code repository. All plugin code - current, past, and in progress - is available here. To prevent abuse, the details are not printed here. To see the information required to mount the share, type "!forkdev login" on IRC.
Below, we see an example of what the source tree would look like. All plugins are stored in a separate directory under the aptly named "plugins" directory. Here we see two plugins, one named "_template" and another named "calc".
"_template" shows what you would see immediately after envoking "!forkdev new". It contains three files, which are described later in this manual. Files in the root of a plugin's directory are the "working set" or "trunk". These are the files that are being edited.
"calc" shows a plugin that has been through two revisions. When you run "!forkdev commit" for the first time, the subdirectory "revisions" is created, and all files from the base (our working set) are saved in yet another subdirectory with the revision number appended. In this case, "calc.0001". The next "!forkdev commit" created version 2.
Now draw your attention to the "current" symlink. This link is created when one runs "!forkdev version", and points to the code that forkb0t is currently looking at himself.
~/forkdev/
`-- plugins
|-- _template
| |-- __init__.py
| |-- _template.conf
| `-- _template.py
`-- calc
|-- __init__.py
|-- calc.conf
|-- calc.py
|-- current -> revisions/calc.0002
`-- revisions
|-- calc.0001
| |-- __init__.py
| |-- calc.conf
| `-- calc.py
`-- calc.0002
|-- __init__.py
|-- calc.conf
`-- calc.py
There are a total of 3 files that are present in the current plugin skeleton.
You will never need to edit this file. In fact, there is nothing in it! It is merely present because Python will refuse to load your code otherwise.
Here is where your main code is. forkb0t loads this file and looks at two functions. One is the help function, which is called when a user wants to know how to use your plugin. The other is the <foo> function, which contains the code you want to run.
When a user needs help with a function, forkb0t attempts to execute help(keyword) from your module. keyword will be a string with the specific trigger the user invoked.
def help(msgtext): return "Converts a set of earth coordinates into a zoneinfo time zone\nUSAGE: " + msgtext + " <latitude> <longitude>"
In the above example, if a user were to send the text "!coord2tz –help" to forkb0t, and "!coord2tz" was a registered keyword for this plugin, the resulting reply from forkb0t would be:
Converts a set of earth coordinates into a zoneinfo time zone USAGE: !coord2tz <latitude> <longitude>
coming soon… for now, just read the comments in template
This file tells forkb0t how to run your plugin code. It is an "ini file" format, with all variables in one section named <foo>.
API version. Choices are 1 (the default) or 2.
Choices are "python" or "mono".
Space separated list of commands that envoke plugin, with * running every time. This variable is REQUIRED. There is no default.
# Example 1 # Will run your plugin when a message starts with !yes or !no keywords=!yes !no
# Example 2 # Will run your code on every message keywords=*
Boolean set to "True" or "False". If true, forkb0t will refuse to run your code if the input passed by a user contains any match to a hardcoded list of items that could build an exploit if passed to a POSIX shell (sh, bash, zsh, etc.). As of this writing, the current list is:
["$", "`", "|", ";", "&", "~", "<<", ">>", '\'\'', '\"', '\\']
Boolean set to "True" or "False". Default is "True". If true, forkb0t will refuse to run your code if the input passed by a user contains any match to a hardcoded list of items that could build an exploit if passed to a POSIX shell (sh, bash, zsh, etc.), but aren't as dangerous as the blacklist (see above). As of this writing, the current list is:
[">", "<", "&", "%", "*", "/"]
Boolean set to "True" or "False". Default is "True". If true, output from your plugin will be prefixed with the nickname of the triggering user if it is the top level command.
Note that regardless of this setting, forkb0t will always add a prefix if the user requests it with a redirection operator. He will also never prefix if sending to user via PM.
One word describing the type of input your plugin should get when being called by a user message (as opposed to indata, which describes data to pass when called internally by other plugins). Can be "terms" (default), which passes all text after keyword; or "raw", which passes the entire unformatted raw IRC string.
# Example 1
# <rushfan> !forksay imma ninja
# ...forkb0t runs forksay("imma ninja")
[forksay]
keywords=!forksay
input=terms
# Example 2
# <krushia> !showraw
# ...forkb0t runs showraw(":krushia!n=krushia@pool-71-168-95-164.cncdnh.east.myfairpoint.net PRIVMSG #gentoo-pr0n :+!showraw")
[showraw]
keywords=!showraw
input=raw
One word describing the type of output your plugin will produce when being called by a user message (as opposed to outdata, which describes data you pass when called internally by other plugins). Can be "terms" (default), which expects you to pass a simple text reply; or "raw", which expects you to output an entire raw IRC string. PLEASE TELL ME IF YOU USE THIS. Can be dangerous.
Require <variable> from environment to equal the value given. If multiple variables are specified, one of each type must be true.
# Plugin only executed if env.superuser="True" and env.network="freenode" if.superuser = True if.network = freenode
# Plugin only executed if env.superuser="True" and env.network="freenode" or "rizon" if.superuser = True if.network = freenode if.network = rizon
Plugin only executed if this logic, when evaluated as Python code, is true. Due to security issues, you may not use anything but alphanumerics.
Boolean, defaults to "False". When set to "True", forkb0t will create an extensive log each time your plugin runs. This includes a complete stack trace, call environment, and pre/post run private variable values. Note that enabling this does slow down execution significantly, and in some cases the short stack trace sent to #b0tcage will be truncated due to the nature of wrapping things with a trace class.
To see all extended debug data produced, look in the file debug/full_output.txt in your plugin directory.
coming soon… for now, just study existing plugins
Since forkb0t is a live system on a remote server, debugging your code is a bit different than what you may be familiar with. Nevertheless, he has an assortment of functions to help you out. But first, one important reminder:
If you crash forkb0t, there is a bug in his core. Meaning, you should never have to worry about crashing him entirely. No need to cower in fear over potential problems in your plugins. Just go at it. In the rare event that he dies, it isn't your fault!
forkb0t will catch any unhandled exceptions in your code. When this happens, he will print a description of the problem and a short stack trace in his debug channel, #b0tcage on Freenode. The user will then get a message telling him something went wrong. This is the extent of debugging by default.
You can get an extensive report every time your plugin runs if you set the option "debug=True" in your plugin's .conf file. When enabled, an assortment of files in the "debug" directory for your plugin on the forkdev share are populated.
| Filename | Description |
|---|---|
| plugin.env | Contains all data about the calling environment (triggering message) |
| private.before.txt | Contains the values of all your plugin's private data before the call |
| <foo>.trace.txt | Contains complete execution trace. Shows every line of plugin code that ran |
| private.after.txt | Contains the values of all your plugin's private data after the call |
| full_output.txt | All of the above files concatenated together (most of the time you should just look here) |
If any line in the output of your plugin starts with one of the following, forkb0t will give it special treatment, as most IRC clients would.
Sends text as a CTCP ACTION. IRC clients will see this as if forkb0t was speaking in third person. For example, "/me eats a taco" would be seen as "forkb0t eats a taco".
NOTE: The same will be done if your output is prefixed with "ACTION" (uppercase). However, this feature will likely be removed in the future, so use "/me" instead.
Sends text via PM to specified user or channel.
NOTE: When this is seen, EVERY line of text sent afterwards is directed to the target (until you use /msg again).
Send the following line of text without parsing further.
Execute a function in forkb0t's core. Currently the only available function is "replug", and takes one optional argument (the plugin to replug).
Send a CTCP PRIVMSG to the target
Send a CTCP reply (via NOTICE) to the target
Ignore the current line. Though there is no technical reason to use this, it can be helpful in code to point out that you are making a multi-line output.
Send text as a NOTICE to the target.
When forkb0t calls a plugin, he creates a file called "plugin.env" that contains various information about the calling conditions. These variables are read-only, and can be retrieved by calling the get function with the name of the variable.
Name of the network channel in which a keyword match resulted in invoking this function call.
The keyword that was matched.
The name of the plugin.
Name of the user who spoke the triggering message.
Equal to "True" if nick is identified.
Equal to "True" if nick is a highly trusted user with commit access. If forkb0t was Gentoo (for example), these would be developers.
Equal to "True" if nick is the b0t owner.
forkb0t maintains a simple key/value pair for each user. This is maintained in a file named "parsed.my.conf". Values here are all strictly formatted, and only written to by functions that are registered to handle this strict format. To get these values, call get with the parameters "nick.variable".
Arbitrary string containing data that can be parsed by an intelligent being and converted to a point on planet Earth
Floating-point number representing angular measurement in degrees, ranging from 0.0 at the prime meridian to 180.0 eastward and −180.0 westward
Floating-point number representing angular measurement in degrees, ranging from 0.0 at the equator to 90.0 at the North Pole and −90.0 at the South Pole
Path to a timeinfo timezone
Two letter country code, uppercase, conforming to ISO 3166-1 alpha-2
Boolean "True" or "False", if "True", forkb0t will force-disable prefixing nickname when speaking to this person.
forkb0t maintains a simple key/value pair for each user. A file called "my.conf" contains data that is not strictly formatted like its "parsed.my.conf" counterpart. This data store is unrestricted R/W. To get these values, call get with the parameters "nick.variable". To set these values, call set with the parameters "nick.variable is value". Beware, these values can contain anything.
Each plugin has its own persistent data storage. A file called "private.txt" contains this data, with sections named after each plugin. This data store is unrestricted R/W. To get these values, call getprivate with the parameter "variable". To set these values, call setprivate with the parameters "variable is value".
get(variable)
get(user.variable)
Retrieves data from forkb0t's storage.
set(user.variable is value)
Sets a variable in forkb0t's user key/value pair datastore.
getprivate(variable)
Retrieves data from your plugin's private storage.
setprivate(variable is value)
Sets a variable in your plugin's private storage.
forkb0t uses the pircbot format, as seen from this pisg sample (when it came time to choose a logging format, I browsed through those supported by pisg and picked this one).
The format of each line is in the table below. <foo> is a variable string, "foo" is the constant string foo.
| What | Format |
|---|---|
| Messages received | <Unix timestamp in ms>" "<message> |
| Messages sent | <Unix timestamp in ms>" »>"<message> |
| Informational (not currently used) | <Unix timestamp in ms>" ***"<message> |
Some links to other bots written in Python. If you want to borrow code from one of these projects, please remember to respect the author's license, and give proper credit in the comment header. Remember that forkb0t's core is GPL v2, so any code in a plugin must be compatible with linking from such. This does NOT mean plugin code must be GPL v2. In fact, it is encouraged to license plugins as openly as possible (such as MIT).
If the links below don't work, try irchelp.org or irc.org
Some channels on IRC that may be helpful to the IRC client programmer.
| URL | Description |
|---|---|
| http://irchelp.org/ | Mostly outdated, but good start for a beginner needing direction |
| http://www.alien.net.au/irc/ | Hasn't been updated in 5 years, but has some information on various ircds that is still of use |
| http://www.irc-plus.org/en/ | IRC+ website. This is (or was?) an attempt at updating IRC, but never gained momentum. |