Usage: slackhal [options]
Options:
-h, --help Show this help.
-t, --token token The slack bot token to use.
-f, --file confing The configuration file to load [default ./slackhal.yml]
--trigger char The char used to detect direct commands [default: !].
--http-handler-port port The Port of the http handler [default: :8080].
-l, --log level Set the log level [default: error].Example of yaml configuration file:
bot:
token: "yourtoken"
trigger: "!"
httpHandlerPort: ":8080"
log:
level: debug
plugins:
disabled:
- echo
- loggerYou can take a look at the builtins plugins to understand how it works.
Your plugin must implement the following interface:
type Plugin interface {
Init( output chan<- *SlackResponse, bot *Bot)
GetMetadata() *Metadata
ProcessMessage(command string, message slack.Msg) bool
Self() interface{}
}Will be called upon plugin login. You can use it if you need to init some stuff.
You can use the Logger as a logger factory for your plugin.
You will use output chan to send your responses back.
Must return a plugin.Metadata struct. This is used to register you plugin.
Will be called when an action or a trigger is found in an incoming message. You can check slack.Msg type here.
To send your response you can send *plugin.SlackResponse instances to the output channel provided by Init above.
This is a dirty hack to access plugin from another plugin.
You will need to expose your plugin structure like:
type Jira struct {(note the Uppercase)
Then implement Self as:
// Self interface implementation
func (h *Jira) Self() interface{} {
return h
}You can then call a plugin from another plugin using the PluginManager map and realize an assertion:
// Check if the plugin is loaded and not disabled
if pg, ok := plugin.PluginManager.Plugins["jira"]; ok {
// Retrieve metadata
info := pg.GetMetadata()
// Check if not disabled
if ! info.Disabled {
// Realize a safe assertion
if jp, found := .(*jiraplugin.Jira); found {
jp.Connect()
...
}
}
}(note that the function you want to call from another plugin needs to be exported as well).
The registration process is done in the init function
func init() {
loggerer := new(logger)
loggerer.Metadata = plugin.NewMetadata("logger")
loggerer.Description = "Logger messages"
loggerer.PassiveTriggers = []plugin.Command{{Name: `(?s:.*)`, ShortDescription: "Log everything", LongDescription: "Will intercept all messages to log them."}}
plugin.PluginManager.Register(loggerer)
}This is where you will initialize the plugin.Metadata struct and add your commands / triggers. It must end with a call to plugin.PluginManager.Register() function call to load you plugin.
NOTE: You should not use this function to init your plugin, this is only meant for registration process. Use the Init function for that.
If you are not creating your plugin under the builtins package you will need to update slackhal.go to import your module like:
_ "github.com/CyrilPeponnet/slackhal/plugins/plugin-jira"where pluginjira is the subfolder where your package is stored.
package builtins
import (
"strings"
"go.uber.org/zap"
"github.com/CyrilPeponnet/slackhal/plugin"
"github.com/slack-go/slack"
)
// echo struct define your plugin
type echo struct {
plugin.Metadata
sink chan<- *plugin.SlackResponse
}
// Init interface implementation if you need to init things
// When the bot is starting.
func (h *echo) Init( output chan<- *plugin.SlackResponse, bot *plugin.Bot) {
h.sink = output
}
// GetMetadata interface implementation
func (h *echo) GetMetadata() *plugin.Metadata {
return &h.Metadata
}
// ProcessMessage interface implementation
func (h *echo) ProcessMessage(command string, message slack.Msg) bool {
if len(strings.Split(message.Text, " ")) == 1 {
return
}
o := new(plugin.SlackResponse)
o.Options = append(o.Options, slack.MsgOptionText(message.Text[strings.Index(message.Text, command)+len(command)+1:len(message.Text)], false))
o.Channel = message.Channel
// This is a test to implement tracking of message
o.TrackerID = 42
h.sink <- o
return true
}
// Self interface implementation
func (h *echo) Self() (i interface{}) {
return h
}
// init function that will register your plugin to the plugin manager
func init() {
echoer := new(echo)
echoer.Metadata = plugin.NewMetadata("echo")
echoer.Description = "Will repeat what you said"
echoer.ActiveTriggers = []plugin.Command{{Name: "echo", ShortDescription: "Parrot style", LongDescription: "Will repeat what you put after."}}
plugin.PluginManager.Register(echoer)
}From the defined Metadata struct:
// Metadata struct
type Metadata struct {
Name string
Description string
Version string
// Active trigers are commands
ActiveTriggers []Command
// Passive triggers are regex parterns that will try to get matched
PassiveTriggers []Command
// Webhook handler
HTTPHandler map[Command]http.Handler
// Only trigger this plugin if the bot is mentionned
WhenMentioned bool
// Disabled state
Disabled bool
}
// Command is a Command implemented by a plugin
type Command struct {
Name string
ShortDescription string
LongDescription string
}Define a command like help. The bot will look for either:
!help@bot help- Direct message starting with
help
Will parse every message to find a match using the POSIX regular expression. If you want to mach all message just put (?s:.*)
You can add a HTTP Handler by defining:
s := newEventHandler()
h.HTTPHandler[plugin.Command{Name: "/jira", ShortDescription: "Jira issue event hook.", LongDescription: "Will trap new issue created and send a notification to channels."}] = sYou handler must implement the http.Handler interface.
Only call the plugin when mentioned or within a DM conversation.
The response channel output will take *SlackResponse struct like:
// SlackResponse struct
type SlackResponse struct {
Channel string
TrackerID int
TrackedTTL int
Options []slack.MsgOption
}Be sure to set the Channel field (you can take it from message.Channel).
- If you set a
userIDas a channel, it will find for your proper DMChannelbefore sending for you. - If you set a channel as a string with a leading
#, it will try to resolve it to the good channel id.
The TrackerID is used if you want to edit sent message later. Your plugin must set the trackerID with a positive integer that will be used as an identifier to edit the message later. The TrackedTTL field is used to set a TTL of tracking. If you send two SlackResponse with the same TrackerID, it will edit the message instead of posting a new one.
The Options field is used to set your message options as described here.
You can find details for advanced attachments formatting here.
TIPS: You can use this website to check the attachment syntax.