Table of Contents

Introduction

For the impatient

Pick a name for your plugin, then do the following, in order. Anyone with common sense will be able to print "Hello, World!".

  1. On IRC, do "!forkdev new <foo>" (<foo> is name for your plugin)
  2. On IRC, do "!forkdev login"
  3. Navigate to <foo> directory on server. Will need to drill down a few levels to get there.
  4. Edit <foo>.py
  5. Edit <foo>.conf
  6. On IRC, ask a superuser (krushia, guzzlefry, or ed-209) to review your code and do "!forkdev up <foo>"

For those who like to read

GO GO GO

Development Tools

forkdev

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

Code repository

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

Files

There are a total of 3 files that are present in the current plugin skeleton.

___init__.py

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.

<foo>.py

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.

Your help function

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>

Your <foo> function

coming soon… for now, just read the comments in template

<foo>.conf

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

API version. Choices are 1 (the default) or 2.

type

Choices are "python" or "mono".

keywords

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=*

shblacklist

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:

["$", "`", "|", ";", "&", "~", "<<", ">>", '\'\'', '\"', '\\']

shgreylist

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:

[">", "<", "&", "%", "*", "/"]

prefixnick

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.

input

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

output

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.

if.<variable>

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

not.<variable>

if

Plugin only executed if this logic, when evaluated as Python code, is true. Due to security issues, you may not use anything but alphanumerics.

debug

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.

Tips, tricks, FAQs and rants

Calling other plugins

coming soon… for now, just study existing plugins

Debugging

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!

Passive error reports

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.

Detailed debugging feature

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)

Reference

<foo> output replacements

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.

/me <text>

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.

/msg < nick | channel > <text>

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).

/say <text>

Send the following line of text without parsing further.

/core <function> [arguments...]

Execute a function in forkb0t's core. Currently the only available function is "replug", and takes one optional argument (the plugin to replug).

/ctcp < nick | channel > <text>

Send a CTCP PRIVMSG to the target

/nctcp < nick | channel > <text>

Send a CTCP reply (via NOTICE) to the target

/macro [text]

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.

/notice < nick | channel > <text>

Send text as a NOTICE to the target.

Data store

env

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.

channel

Name of the network channel in which a keyword match resulted in invoking this function call.

keyword

The keyword that was matched.

plugin

The name of the plugin.

nick

Name of the user who spoke the triggering message.

identified

Equal to "True" if nick is identified.

superuser

Equal to "True" if nick is a highly trusted user with commit access. If forkb0t was Gentoo (for example), these would be developers.

master

Equal to "True" if nick is the b0t owner.

parsed.my

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".

location

Arbitrary string containing data that can be parsed by an intelligent being and converted to a point on planet Earth

longitude

Floating-point number representing angular measurement in degrees, ranging from 0.0 at the prime meridian to 180.0 eastward and −180.0 westward

latitude

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

timezone

Path to a timeinfo timezone

countrycode

Two letter country code, uppercase, conforming to ISO 3166-1 alpha-2

noprefixnick

Boolean "True" or "False", if "True", forkb0t will force-disable prefixing nickname when speaking to this person.

my

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.

private

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".

Core functions

get

get(variable)
get(user.variable)

Retrieves data from forkb0t's storage.

set

set(user.variable is value)

Sets a variable in forkb0t's user key/value pair datastore.

getprivate

getprivate(variable)

Retrieves data from your plugin's private storage.

setprivate

setprivate(variable is value)

Sets a variable in your plugin's private storage.

File Formats

forkb0t logs

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>

Links

Other Python Bots

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).

IRC Specifications/Drafts/Infos

If the links below don't work, try irchelp.org or irc.org

RFCs
Other server drafts
Server numerics info
CTCP/DCC/Colors/etc. (client things)

IRC Channels

Some channels on IRC that may be helpful to the IRC client programmer.

Misc Sites

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.

The Future

Support other protocols