-
Notifications
You must be signed in to change notification settings - Fork 4
Dev
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
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.Handler
s 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 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.
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 PluginProvider
s also provide a NewPlugin()
method to wrap &myPlugin{}
more neatly.