Fully Customizable Haskell IRC Bot
##Table of Contents
#Introduction joebot2 is an upgrade from the original joe_bot that runs on irc.freenode.net #roboclub. joebot2 was designed to be an easy to customize and upgrade haskell irc bot.
#Installation
To install joe_bot, you need the latest haskell platform.
Afterwards, just run cabal install in the directory containing
joebot.cabal.
To run after installing, just run joe_bot.
#The Basics
The bare minimum for Main.hs is:
import Config
main = joebot defaultConfigThis will run joebot2 with the default configurations specified in Config.hs.
Changing the configuration is relatively simple (note the underscore before each field name):
{-# LANGUAGE OverloadedStrings #-}
import Config
main = joebot $ defaultConfig
{ _nick = "test_bot"
, _chan = "#haskell"
, _pass = Just "fake_password"
}More advanced users will note that joebot2 uses the
Control.Lens
library extensively. The above code snippet can be then rewritten to use lenses
(you must import Core to access the lenses):
{-# LANGUAGE OverloadedStrings #-}
import Config
import Control.Lens
import Core
main = joebot $ defaultConfig
& nick .~ "test_bot"
& chan .~ "#haskell"
& pass .~ Just "fake_password"Whether doing so is overkill is an exercise left to the reader.
The Config type has the following fields:
data Config = Config -- Field Explanation Default Value
{ nick :: Text -- nickname of bot "default-bot"
, rname :: Text -- real name of bot "ircbot"
, server :: Text -- irc server hostname "irc.freenode.net"
, port :: Int -- port 6667
, chan :: Text -- irc channel "#joebot-test"
, pass :: Maybe Text -- password for NickServ Nothing
}There are 3 more fields, but these are the fields that should be changed to run joe_bot as something other than "default-bot".
#Rolling Your Own Plugins joebot2 was designed to make it easy to add plugins. Custom plugins are separated into two concepts: custom commands and plugin processes.
Note: it is recommended that you add the following line
{-# LANGUAGE OverloadedStrings #-}to the top of every .hs file. Furthermore, it is recommended that you import Data.Text like so:
import qualified Data.Text as TThis is due to the fact that many of the functions provided for plugin support rely heavily on Data.Text for performance and compatibility purposes.
##Custom Commands
A Command is roughly the type
data Command = Command
{ cmdName :: Text
, arity :: Int
, runCmd :: Text -> Maybe Text -> [Text] -> Net ()
, help :: Text
}Here is a brief description of each of the fields:
cmdNameis the name of the command (e.g. "!quit").arityis the number of arguments you expect the command to take.runCmdtakes in a nick, a channel, and arguments and executes the command.helpis shown when the command is used incorrectly.
###Example A simple example of a custom command is found in the Dice Plugin:
roll = Command "!roll" 1 rollDie "!roll <num_dice>d<num_sides>"Here, we see that Command is the constructor, and it takes in the
following arguments:
"!roll"is the name of the command.1roll expects 1 argument, namely an argument of the form "#d#".rollDieis the function that is invoked when the command is run."!roll <num_dice>d<num_sides>"is shown when an inappropriate number of arguments is given to roll.
###Helper Functions
Note that runCmd returns a Net (). In order to not expose the underlying
implementation of Net, three functions have been provided in
the Core module:
write :: Text -> Text -> Net ()
privmsg :: Text -> Maybe Text -> Text -> Net ()
action :: Text -> Net ()The functions do the following:
write s twrites to the server with the format"s t \r\n". This function is very general, so usage is not recommended. However, this function is useful for writing messages that are not PRIVMSG (e.g. PART, QUIT).privmsg nick chan textwrites a private message to the server. Ifchanis not specified (i.e.Nothing), the message is sent as a private message to the user with the nicknamenick. Otherwise the message is written to the channel specified bychan.action textwill make the bot perform an action on the channel specified by the initial configuration.
##Plugin Processes
Sometimes it is desirable for a plugin to have persistent state that joebot2 can access.
Going in to Core.Types
and changing Net () is not recommended as it breaks the plugin
abstraction. So how do we add new state to joebot2?
The solution is Plugin Processes. Simply put, a Plugin Process is an IO thread that reads the command argument data from a channel (or channels) a la the Actor Model. Due to the multithreaded nature of plugin processes, please note that you are responsible for your own race conditions.
###Plugins.Utils Types Plugins/Utils.hs provides some helper functions and types for writing your own plugin process.
The two types exported are:
type Hook = Text -> Text -> Net ()
type Proc = Chan Msg -> IO ()A Hook is a command that is run when a user joins, parts, or quits a channel. Currently
there are two types of hooks in Config - jhooks and phooks. The former hook is
run when a user joins and the latter when a user parts/quits.
A Proc is the type of a plugin process. Note that this is only a suggestion. Using
Proc allows you to interface with the functions exported by PluginUtils.
###Plugins.Utils Functions The two functions exported are:
spawnProc :: Config
-> Proc
-> [Chan Msg -> Command]
-> [Chan Msg -> Hook]
-> [Chan Msg -> Hook]
-> Bool
-> IO Config
updateConfig :: Config
-> Chan Msg
-> [Chan Msg -> Command]
-> [Chan Msg -> Hook]
-> [Chan Msg -> Hook]
-> Bool
-> ConfigBoth functions take in lists of commands, jhooks, and phooks.
spawnProctakes aProc, creates a channel, and then runs the process. It then takes the newly created channel, applies all the commands and hooks with it and updates the configuration. The final boolean argument is to tell joe_bot whether or not to send aQuitmessage to the process upon shutdown.updateConfigtakes a channel and applies all the commands and hooks with it and then updates the configuration.
#Examples There are few example plugins packaged in with joebot2:
- Dice is a custom command that allows joebot to roll any number of any kind of dice. The annotated source gives a detailed example of how this plugin was written and may be helpful to those who want to write plugins of their own.
- Mail is a mail server that allows people to send messages to others even when they're offline. This shows an example of how a plugin process works, along with an example of a join hook.