Skip to content
Alex Bramley edited this page Jan 2, 2012 · 3 revisions

Directory Structure

lib/            -- contains various library building blocks
lib/db          -- wrapper around mgo.Database with some utility functions
lib/factoids    -- combines mgo.Collection and Factoid structs to create factoids collection
lib/quotes      -- combines mgo.Collection and Quote structs to create quotes collection
lib/util        -- random helper functions that don't fit usefully anywhere else

sp0rkle/        -- contains the main bot codebase
sp0rkle/base    -- contains basic types and interfaces so import loops can be avoided
sp0rkle/bot     -- contains the Sp0rkle struct, associated methods and IRC event handlers
sp0rkle/drivers -- contains bot drivers, one per subdirectory

util/           -- contains various ancillary binaries for e.g. importing data into mongodb

Events

Sp0rkle is an event based bot built on top of goirc/event and goirc/client. The event library is a very generic mechanism for associating a set of functions -- which all MUST have the same function signature -- with a name that can then be used to trigger those functions. The IRC client library exposes it's internal registry so that users of the library may register functions to process incoming IRC lines; to help enforce the function signature, it defines an IRCHandler type and a function to create generic event.Handlers from this type:

// Package names fully-qualified for clarity (hopefully)
type client.IRCHandler func(*client.Conn, *client.Line)
func client.NewHandler(f client.IRCHandler) event.Handler {
    return event.NewHandler(func(ev ...interface{}) {
        f(ev[0].(*client.Conn), ev[1].(*client.Line))
    })
}

The events dispatched by the IRC client library are handled in sp0rkle/bot/handlers.go, which (mostly) just translates them to new events, of type bot.BotHandler rather than client.IRCHandler), using these types:

type bot.BotHandler func(*bot.Sp0rkle, *base.Line)
func bot.NewHandler(f bot.BotHandler) event.Handler { ... }

Drivers

Drivers are the main abstraction within the bot. They register event handlers and SHOULD perform either a single function or a group of closely related functions. In general, they will also be manipulating the backend database, storing and retrieving data via a wrapped mgo.Collection object. Driver authors are encouraged to create this wrapper under lib/ so that it may be used by importers/exporters.

In order for a driver to register to handle certain events, it MUST provide a RegisterHandlers method which takes an event.Registry. It can then define add event handlers for bot events like so:

var driverName string = "mydriver"

func md_privmsg(bot *bot.Sp0rkle, line *base.line) {
    md := bot.GetDriver(driverName).(*myDriver)
    // do stuff with md, bot and line
}

func (md *myDriver) RegisterHandlers(r event.Registry) {
    r.AddHandler("bot_privmsg", bot.NewHandler(md_privmsg)
}

If the driver requires it's own event types as well as the ones that the bot triggers, it MUST also define the event type and a wrapper function to unbox the interface values for the real handler as above. Given that the internal namespace within the event registry is flat, drivers MAY also register handlers for IRC events, but this is not recommended. Another caveat of this is that drivers SHOULD ensure any events they dispatch for themselves are named in a manner that won't conflict with other drivers potentially doing the same thing.

Plugins

Drivers MAY also register "plugins" with (currently just) the factoid driver. These are used to perform transformations on the factoid value which is about to be sent in reply to a lookup, but again need to jump through some hoops to perform state injection and type unboxing. Inconveniently, these currently follow a somewhat different pattern to event handlers, though with the same underlying idea.

Drivers wanting to provide plugins MUST provide a RegisterPlugins method that takes a base.PluginManager, as well as a concrete struct type that implements the Plugin interface and injects any state needed. This is achieved like so:

type myPlugin struct {
    provider *myDriver
    processor func(*myDriver, string, *base.Line) string
}

func (mp *myPlugin) Apply(value string, line *base.Line) string {
    mp.processor(mp.provider, value, line)
}

func md_process(md *myDriver, value string, line *base.Line) string {
    // do stuff with md, value, and line, return processed value
}

func (md *myDriver) RegisterPlugins(pm base.PluginManager) {
    pm.AddPlugin(&myPlugin{provider: md, processor: md_process})
}

This is a little clunky and may change slightly in the future, probably by insisting via an interface that PluginProviders also provide a NewPlugin() method to wrap &myPlugin{} more neatly.

Clone this wiki locally