Skip to content

Plugins #4 Configuration

Stephen Brennan edited this page Apr 25, 2017 · 2 revisions

Up until now, we've assumed that a plugin is a happy, small piece of code that has nothing to do with the outside world. But to do interesting things, usually you will need some configuration info. Here are two examples of things that require configuration:

  1. Imagine you're making a plugin that replies or reacts to messages that match a regex (hey!). You probably don't want to have to write code every time you come up with a new trigger, response, or reaction. Instead, you should be able to write a configuration file that specifies them, and your code should simply follow the configuration.
  2. If you are connecting to an API outside of Slack (like GitHub), you'll want to store its keys somewhere, and a config file is as good as any other place.

In lower level languages, parsing configuration files can be a real chore, involving lots of silly extra code. Thankfully, Slacksoc has a very nice way to get rid of that.

YAML!

As you've seen briefly earlier, the bot has a configuration file, config.yaml. This has some bot configuration (like the API key), as well as a list of plugins. One thing you may not have realized is that the list of plugins can contain more than just their names! For instance, here is an example configuration for a respond plugin:

token: foobar
plugins:
  - name: Respond
    responses:
      - trigger: ^(yo|hey|hi|hello|sup),? slacksoc$
        replies: ["hello", "wassup", "yo"]
      - trigger: ^((good)?bye|adios),? slacksoc$
        replies: ["goodbye"]
      - trigger: (?i)i love you
        reacts: ["heart"]
  # etc

All of the keys and values after name are available to provide configuration to your plugin! This information comes to your Plugin Constructor in the third argument. It comes in the format of a PluginConfig, which is just:

type PluginConfig map[string]interface{}

It's kind of half-parsed. It's no longer the YAML that it came in as, but it would require a lot of ugly type assertions to get this data into a nice format. So, we turn to a wonderful library to help us out: mapstructure.

mapstructure!

Mapstructure's only job is to take maps like the one you get from slacksoc, and allow you to convert them into instances of a struct. Put simply, it takes the keys from the map and assigns their values to the struct you give it, doing casting as necessary. Here is an example of the structs used to parse the Respond config file shown above:

/*
An entry to associate a trigger with multiple potential replies.
*/
type response struct {
	Trigger string
	trigger *regexp.Regexp
	Replies []string
	Reacts  []string
}

/*
This is a fairly simple plugin that allows you to configure triggers and
responses, nearly identical to traditional slackbot.
*/
type respond struct {
	Responses []response
}

Note that some fields are upper case and some are lower-case. The upper-case ones are "public" and they are the only ones that mapstructure will read from the configuration file. The lower case ones (like trigger) are useful to fill out after the config is loaded.

Mapstructure does one funny thing - to fill up a field named Replies, it will look for the map key with the lower-case version of its name -- replies. Mapstructure will only lower the case of the first letter, so to fill ApiKey it will look for the key apiKey. Just keep that in mind when defining structs and then writing config files with them. You can read their documentation for more thorough information.

Although you could use the Mapstructure library directly, there's one thing it doesn't handle. Mapstructure is perfectly happy if struct fields are missing values, and it's also perfectly happy if the YAML file contains fields your struct doesn't have. Those are both things you'll probably want to raise errors for. So, slacksoc provides a convenient Bot.Configure() function to wrap up Mapstructure's functionality with these error checks. The first argument is the config object you want to parse. The second argument is a pointer to the destination struct to fill. The final argument is a slice of names (the upper case version) of required fields. No errors are returned because if any occur, it's a crash! This code, directly from the Respond plugin, illustrates how simple it is:

var respond respond
bot.Configure(config, &respond, []string{"Responses"})

TADA

Using slacksoc's built-in configuration, your plugins' behavior can be specified completely from a single point of truth: your bot's conifguration file.

Clone this wiki locally