Simple, pluggable bot framework for Slack chat.
You can grab the source code using go get github.com/trinchan/slackbot
and install like usual. go install github.com/trinchan/slackbot
Slackbot uses environment variables for all configuration, with domain wide variables prefixed by the domain name in title case (e.g. MYDOMAIN_IN_URL
for MyDomain's incoming webhook URL). This makes it easy to support multiple domains and deploy to Heroku. Make sure to set a PORT
environment variable defining what port to run your bot on.
An example environment variable list:
BIJIN_TIMEZONE=Asia/Tokyo
MYDOMAIN_OUT_TOKEN=AVerySecretToken
MYDOMAIN_IN_URL=https://hooks.slack.com/services/AAAAAAAAA/BBBBBBBBB/AnotherVerySecretToken
PORT=5000
If you don't already have an Incoming Webhook setup for your bot, you'll want to start here. Set one up (make sure it's enabled) and don't be afraid to read through the setup instructions. You're after the "Webhook URL" that slack generates for you.
https://hooks.slack.com/services/AAAAAAAAA/BBBBBBBBB/YourSecretToken123456789
For each domain, set an environment variable DOMAIN_IN_URL
to this URL.
This framework can respond to "slash commands" and "outgoing webhooks" If you want users to be able to silently type /ping
, and have the ping-bot respond in their channel, then you'll want to set up "slash commands". Each bot will need it's own command setup. The other option is to configure an outgoing webhook with a symbol for the prefix. Exe: !ping
. This option only requires one configuration, but the commands will be entered into the channel as regular messages.
I use an Outgoing Webhook
- Add a new Outgoing Webhook Integration.
- Here, you can tell slack to ONLY pay attention to a specific channel, or to simply listen to all public channels. Outgoing Webhooks can not listen to private channels/direct messages.
- For each domain, set an environment variable
DOMAIN_OUT_TOKEN
to your integration's token. This is used to verify payloads come from Slack. - The {trigger_word} should only be one character (preferrably a symbol, such as ! or ?) and typing
{trigger_word}ping
will trigger the Ping bot. - The URL should follow the following format:
your_address.com:port/slack_hook
(no trailing /) The bot will respond to commands of the form{trigger_word}bot param param param
in the specified channels
Alternatively, each bot you make can respond to a corresponding Slash Command.
- Add a new slash command, use the bot's name as the name of the command.
- The URL should follow the following format:
your_address.com:port/slack
(no trailing /) - You want to use POST.
- For each bot, set an environment variable
BOTNAME_SLACK_TOKEN
to your slash command's token. This is used to verify payloads come from Slack. - Repeat for each bot you want to enable.
The bot will respond to commands of the form /bot param param param
After setting up the proper environment variables, deploying to heroku should be as simple using the heroku-go-buildpack with a one line modification to run go generate ./...
before installing to generate the plugin import file.
- Create a new package and implement the Robot interface.
- In importer/importer.sh, add the path to your package to the robots array.
- Run
go generate ./...
to generateinit.go
which will import your bot. - Rebuild slackbot and deploy.
If you use Sublime Text or another editor which supports snippets for development, then you can simply add and use the included snippet to easily generate a template Robot based on the filename. Otherwise, refer to the template below.
package test
import "github.com/trinchan/slackbot/robots"
type bot struct{}
// Registers the bot with the server for command /test.
func init() {
r := &bot{}
robots.RegisterRobot("test", r)
}
// All Robots must implement a Run command to be executed when the registered command is received.
func (r bot) Run(p *robots.Payload) string {
// If you (optionally) want to do some asynchronous work (like sending API calls to slack)
// you can put it in a go routine like this
go r.DeferredAction(p)
// The string returned here will be shown only to the user who executed the command
// and will show up as a message from slackbot.
return "Text to be returned only to the user who made the command."
}
func (r bot) DeferredAction(p *robots.Payload) {
// Let's use the IncomingWebhook struct defined in payload.go to form and send an
// IncomingWebhook message to slack that can be seen by everyone in the room. You can
// read the Slack API Docs (https://api.slack.com/) to know which fields are required, etc.
// You can also see what data is available from the command structure in definitions.go
// Alternatively, you can make a SlashCommandResponse, with the same fields, and call
// reponse.Send(p)
response := &robots.IncomingWebhook{
Channel: p.ChannelID,
Username: "Test Bot",
Text: "Hi there!",
IconEmoji: ":ghost:",
UnfurlLinks: true,
Parse: robots.ParseStyleFull,
}
response.Send()
}
func (r bot) Description() string {
// In addition to a Run method, each Robot must implement a Description method which
// is just a simple string describing what the Robot does. This is used in the included
// /c command which gives users a list of commands and descriptions
return "This is a description for TestBot which will be displayed on /bots"
}
If you are using Slash Commands, you'll need to add a new slash command integration for each bot you add.
If you are not using Heroku, making a env.sh
file which exports your environment variables and running slackbot via
source env.sh && slackbot
makes for a convenient one-liner.
If you see output similar to below and you have the commands enabled in your Slack integration, you're ready to go!
2015/06/07 23:26:56 Registered: wiki
2015/06/07 23:26:56 Registered: store
2015/06/07 23:26:56 Registered: roll
2015/06/07 23:26:56 Registered: ping
2015/06/07 23:26:56 Registered: nihongo
2015/06/07 23:26:56 Registered: bots
2015/06/07 23:26:56 Registered: decide
2015/06/07 23:26:56 Registered: bot
2015/06/07 23:26:56 Registered: bijin
2015/06/07 23:26:56 Starting HTTP server on 13748
If you prefer to use slackbot as a library rather than a binary:
- Create a new package and implement the Robot interface as above
- use a
main
function instead of aninit
function:
package main
import (
"github.com/trinchan/slackbot/robots"
"github.com/trinchan/slackbot/server"
)
type bot struct{}
// Starts the server with a bot for command /test.
func main() {
bots := map[string][]robots.Robot{"test": []robots.Robot{&bot{}}}
server.Main(bots)
}