slackscot

package module
v1.49.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 23, 2021 License: MIT Imports: 24 Imported by: 1

README

License GoDoc Build Go Report Card Test Coverage Maintainability Mentioned in Awesome Go Latest Version

logo.svg

name.svg

Overview

Slackscot is a slack bot core written in Go. Think of it as the assembly kit to making your own friendly slack bot. It comes with a set of plugins you might enjoy and a friendly API for you to realize your ambitious dreams (if you dreams include this sort of thing).

Requirements

Go 1.11 or above is required, mostly for go module support.

Features

  • Support for reactions to message updates. slackscot does the following:

    • Keeps track of plugin action responses and the message that triggered them

    • On message updates:

      1. Update responses for each triggered action

      2. Delete responses that aren't triggering anymore (or result in errors during the message update)

    • On deletion of triggering messages, responses are also deleted

    • Limitation: Sending a message automatically splits it into multiple slack messages when it's too long. When updating messages, this spitting doesn't happen and results in an message too long error. Effectively, the last message in the initial response might get deleted as a result. Handling of this could be better but that is the current limitation 😕

  • Support for threaded replies to user message with option to also broadcast on channels (disabled by default). See configuration example below where both are enabled.

    • Plugin actions may also explicitely reply in threads with/without broadcasting via AnswerOption
  • Concurrent processing of unrelated messages with guarantees of proper ordering of message updates/deletions

  • Simple extensible storage API for persistence in two flavors: StringStorer and BytesStorer. Both are basic key:value maps. A default file-based implementation is provided backed by leveldb

  • Implementation of StringStorer backed by Google Cloud Datastore. See datastoredb's godoc for documentation, usage and example.

  • In-memory implementation of StringStorer wrapping any StringStorer implementation to offer low-latency and potentially cost-saving storage implementation well-suited for small datasets. Plays well with cloud storage like the [datastoredb]((https://godoc.org/github.com/alexandre-normand/slackscot/store/datastoredb) See inmemorydb's godoc for documentation, usage and example.

  • Support for various configuration sources/formats via viper

  • Support for various ways to implement functionality:

    1. scheduled actions: run something every second, minute, hour, week. Oh Monday is a plugin that demos this by sending a Monday greeting every Monday at 10am (or the time you configure it to).
    2. commands: respond to a command directed at your slackscot. That means something like @slackscot help or a direct message help sent to slackscot.
    3. hear actions (aka "listeners"): actions that evaluated for a match on every message that slackscot hears. You'll want to make sure your Match function isn't too heavy. An example is the "famous" finger quoter plugin
  • Experimental and subject to change: Testing functions to help validate plugin action behavior (see example in triggerer_test.go). Testing functions are found in assertplugin and assertanswer

  • Built-in help plugin supporting a decently formatted help message as a command listing all plugins' actions. If you'd like some actions to not be shown in the help, you can set Hidden to true in its ActionDefinition (especially useful for hear actions)

  • The plugin interface as a logical grouping of one or many commands and hear actions and/or scheduled actions

  • Support for injected services providing plugins easy access to an optionally caching user info and a logger.

Demo

  • slackscot deleting a triggered reaction after seeing a message updated that caused the first action to not trigger anymore and a new action to now trigger (it makes sense when you see it)

different-action-triggered-on-message-update

  • slackscot updating a triggered reaction after seeing a triggering message being updated

same-action-answer-update-on-message-update

  • slackscot deleting a reaction after seeing the triggering message being deleted

reaction-deletion-on-message-delete

  • slackscot threaded replies enabled (with broadcast => on)

threaded-reply-with-broadcast

The Name

The first concrete bot implementation using this code was youppi, named after the great mascot of the Montreal Expos and, when the Expos left Montreal, the Montreal Canadiens.

Slackscot is a variation on the expected theme of slackbot with the implication that this is the core to more than just a regular bot. You know, a friendly company mascot that hangs out on your slack.

Concepts

  • Commands: commands are well-defined actions with a format. Slackscot handles all direct messages as implicit commands as well as @mention <command> on channels. Responses to commands are directed to the person who invoked it.

  • Hear actions: those are listeners that can potentially match on any message sent on channels that slackscot is a member of. This can include actions that will randomly generate a response. Note that responses are not automatically directed to the person who authored the message triggering the response (although an implementation is free to use the user id of the triggering message if desired).

Create Your Own Slackscot

Slackscot provides the pieces to make your mascot but you'll have to assemble them for him/her to come alive. The easiest to get started is to look at a real example: youppi.

youppi running

The godoc is also a good reference especially if you're looking to implement something like a new implementation of the storer interfaces.

Assembling the Parts and Bringing Your slackscot to Life

Here's an abbreviated example of how youppi does it:

package main

import (
	"github.com/alexandre-normand/slackscot"
	"github.com/alexandre-normand/slackscot/config"
	"github.com/alexandre-normand/slackscot/plugins"
	"github.com/alexandre-normand/slackscot/store"
	"github.com/spf13/viper"
	"gopkg.in/alecthomas/kingpin.v2"
	"log"
	"os"
	"io"
)

const (
	name           = "youppi"
)

func main() {
	kingpin.Version(VERSION)
	kingpin.Parse()

	// TODO: initialize storer implementations required by plugins and do any other initialization 
	// required
	...

	// This is the where we create youppi with all of its plugins
	youppi, err := slackscot.NewBot(name, v, options...).
		WithPlugin(plugins.NewKarma(karmaStorer)).
		WithPlugin(plugins.NewTriggerer(triggererStorer)).
		WithConfigurablePluginErr(plugins.FingerQuoterPluginName, func(conf *config.PluginConfig) (p *slackscot.Plugin, err error) { return plugins.NewFingerQuoter(conf) }).
		WithConfigurablePluginCloserErr(plugins.EmojiBannerPluginName, func(conf *config.PluginConfig) (c io.Closer, p *slackscot.Plugin, err error) {
			return plugins.NewEmojiBannerMaker(conf)
		}).
		WithConfigurablePluginErr(plugins.OhMondayPluginName, func(conf *config.PluginConfig) (p *slackscot.Plugin, err error) { return plugins.NewOhMonday(conf) }).
		WithPlugin(plugins.NewVersionner(name, version)).
		Build()
	defer youppi.Close()

	if err != nil {
		log.Fatal(err)
	}

	err = youppi.Run()
	if err != nil {
		log.Fatal(err)
	}
}

Configuration Example

You'll also need to define your configuration for the core, used built-in plugins and any configuration required by your own custom plugins (not shown here). Slackscot uses viper for loading configuration which means that you are free to use a different file format (yaml, toml, env variables, etc.) as desired.

{
   "token": "your-slack-bot-token",
   "debug": false,
   "responseCacheSize": 5000,
   "userInfoCacheSize": 0,
   "maxAgeHandledMessages": 86400,
   "timeLocation": "America/Los_Angeles",
   "storagePath": "/your-path-to-bot-home",
   "replyBehavior": {
      "threadedReplies": true,
      "broadcastThreadedReplies": true
   },
   "plugins": {
      "ohMonday": {
   	     "channelIDs": ["slackChannelId"]
      },
      "fingerQuoter": {
         "frequency": "100",
         "channelIDs": []
      },
      "emojiBanner": {
         "figletFontUrl": "http://www.figlet.org/fonts/banner.flf"
      }
   }
}

Creating Your Own Plugins

It might be best to look at examples in this repo to guide you through it:

  • The simplest plugin with a single command is the versioner
  • One example of scheduled actions is oh monday
  • One example of a mix of hear actions / commands that also uses the store api for persistence is the karma

Contributing

  1. Fork it (preferrably, outside the GOPATH as per the new go modules guidelines)
  2. Make your changes, commit them (don't forget to go build ./... and go test ./...) and push your branch to your fork
  3. Open a PR and fill in the template (you don't have to but I'd appreciate context)
  4. Check the code climate and travis PR builds. You might have to fix things and there's no shame if you do. I probably won't merge something that doesn't pass CI build but I'm willing to help to get it to pass 🖖.

Open-telemetry integration

Slackscot now supports integration with opentelemetry. To aid with the addition of new metrics, you can find a gowrap template here. To add a metrics to a new interface, you can do something like

gowrap gen -p . -i <interfaceName> -t opentelemetry.template -o <interfaceName>metrics.go

When updating the template, you should consider running go generate in order to refresh the already generated files with the template changes.

Some Credits

slackscot uses Norberto Lopes's Slack API Integration found at https://github.com/nlopes/slack. The core functionality of the bot is previously used James Bowman's Slack RTM API integration and was heavily inspired by talbot, also written by James Bowman.

Documentation

Overview

Package slackscot provides the building blocks to create a slack bot.

It is easily extendable via plugins that can combine commands, hear actions (listeners) as well as scheduled actions. It also supports updating of triggered responses on message updates as well as deleting triggered responses when the triggering messages are deleted by users.

Additionally, slackscot supports concurrent processing of messages. It also guarantees that updates and deletions of messages are processed in order relative to the original message they refer to.

Slackscot integrates with opentelemetry (https://github.com/open-telemetry/opentelemetry-go) for metrics and can support integrating with various metrics exporter.

Plugins also have access to services injected on startup by slackscot such as:

  • UserInfoFinder: To query user info
  • SLogger: To log debug/info statements
  • EmojiReactor: To emoji react to messages
  • FileUploader: To upload files
  • RealTimeMessageSender: To send unmanaged real time messages outside the normal reaction flow (i.e. for sending many messages or sending via a scheduled action)
  • SlackClient: For advanced access to all the slack APIs via https://godoc.org/github.com/slack-go/slack#Client

Example code (from https://github.com/alexandre-normand/youppi):

	package main

	import (
		"github.com/alexandre-normand/slackscot"
		"github.com/alexandre-normand/slackscot/config"
		"github.com/alexandre-normand/slackscot/plugins"
 		"gopkg.in/alecthomas/kingpin.v2"
 		"io"
	)

	func startPrometheusExporter(port int) (pusher *push.Controller, err error) {
		pusher, hf, err := prometheus.InstallNewPipeline(prometheus.Config{DefaultSummaryQuantiles: []float64{0.25, 0.5, 0.9, 0.95, 0.99}, OnError: func(err error) {
			log.Printf("Error on prometheus exporter: %w", err)
		}})

		if err != nil {
			return nil, err
		}

		http.HandleFunc("/metrics", hf)
		go func() {
			_ = http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
		}()

		return pusher, nil
	}

	func main() {
		// TODO: Parse command-line, initialize viper and instantiate Storer implementation for needed for some plugins

		// Optional: Initialize opentelemetry exporter for instrumentation, if desired
		exporter, err := startPrometheusExporter(*prometheusPort)
		if err != nil {
			log.Fatalf("Error starting prometheus [%s]", err.Error())
		}
		defer exporter.Stop()

		youppi, err := slackscot.NewBot("youppi", v, options...).
			WithPlugin(plugins.NewKarma(karmaStorer)).
			WithPlugin(plugins.NewTriggerer(triggererStorer)).
			WithConfigurablePluginErr(plugins.FingerQuoterPluginName, func(conf *config.PluginConfig) (p *slackscot.Plugin, err) { return plugins.NewFingerQuoter(c) }).
			WithConfigurablePluginCloserErr(plugins.EmojiBannerPluginName, func(conf *config.PluginConfig) (c io.Closer, p *slackscot.Plugin, err) { return plugins.NewEmojiBannerMaker(c) }).
			WithConfigurablePluginErr(plugins.OhMondayPluginName, func(conf *config.PluginConfig) (p *slackscot.Plugin, err) { return plugins.NewOhMonday(c) }).
			WithPlugin(plugins.NewVersionner(name, version)).
			Build()
		defer youppi.Close()

		if err != nil {
			log.Fatal(err)
		}

		err = youppi.Run()
		if err != nil {
			log.Fatal(err)
		}
	}

Index

Constants

View Source
const (
	// ThreadedReplyOpt is the name of the option indicating a threaded-reply answer
	ThreadedReplyOpt = "threadedReply"
	// BroadcastOpt is the name of the option indicating a broadcast answer
	BroadcastOpt = "broadcast"
	// ThreadTimestamp is the name of the option indicating the explicit timestamp of the thread to reply to
	ThreadTimestamp = "threadTimestamp"
	// EphemeralAnswerToOpt marks an answer to be sent as an ephemeral message to the provided userID
	EphemeralAnswerToOpt = "ephemeralMsgToUserID"
)
View Source
const (
	// VERSION represents the current slackscot version
	VERSION = "1.49.0"
)

GENERATED and MANAGED by giddyup (https://github.com/alexandre-normand/giddyup)

Variables

This section is empty.

Functions

func ApplyAnswerOpts

func ApplyAnswerOpts(opts ...AnswerOption) (sendOptions map[string]string)

ApplyAnswerOpts applies answering options to build the send configuration

func NewSLogger

func NewSLogger(log *log.Logger, debug bool) (l *sLogger)

NewSLogger creates a new Slackscot logger provided with an interface logger and a debug flag

func NewchatDriverWithTelemetry added in v1.42.0

func NewchatDriverWithTelemetry(base chatDriver, name string, meter metric.Meter) chatDriverWithTelemetry

NewchatDriverWithTelemetry returns an instance of the chatDriver decorated with open telemetry timing and count metrics

Types

type ActionDefinition

type ActionDefinition struct {
	// Indicates whether the action should be omitted from the help message
	Hidden bool

	// Matcher that will determine whether or not the action should be triggered
	Match Matcher

	// Usage example
	Usage string

	// Help description for the action
	Description string

	// Function to execute if the Matcher matches
	Answer Answerer
}

ActionDefinition represents how an action is triggered, published, used and described along with defining the function defining its behavior

type ActionDefinitionWithID

type ActionDefinitionWithID struct {
	ActionDefinition
	// contains filtered or unexported fields
}

ActionDefinitionWithID holds an action definition along with its identifier string

type Answer

type Answer struct {
	Text string

	// Options to apply when sending a message
	Options []AnswerOption

	// BlockKit content blocks to apply when sending the message
	ContentBlocks []slack.Block
}

Answer holds data of an Action's Answer: namely, its text and options to use when delivering it

type AnswerOption

type AnswerOption func(sendOpts map[string]string)

AnswerOption defines a function applied to Answers

func AnswerEphemeral

func AnswerEphemeral(userID string) AnswerOption

AnswerEphemeral sends the answer as an ephemeral message to the provided userID

func AnswerInExistingThread

func AnswerInExistingThread(threadTimestamp string) AnswerOption

AnswerInExistingThread sets threaded replying with the existing thread timestamp

func AnswerInThread

func AnswerInThread() AnswerOption

AnswerInThread sets threaded replying

func AnswerInThreadWithBroadcast

func AnswerInThreadWithBroadcast() AnswerOption

AnswerInThreadWithBroadcast sets threaded replying with broadcast enabled

func AnswerInThreadWithoutBroadcast

func AnswerInThreadWithoutBroadcast() AnswerOption

AnswerInThreadWithoutBroadcast sets threaded replying with broadcast disabled

func AnswerWithoutThreading

func AnswerWithoutThreading() AnswerOption

AnswerWithoutThreading sets an answer to threading (and implicitly, broadcast) disabled

type Answerer

type Answerer func(m *IncomingMessage) *Answer

Answerer is what gets executed when an ActionDefinition is triggered. To signal the absence of an answer, an action should return nil

type Builder

type Builder struct {
	// contains filtered or unexported fields
}

Builder holds a slackscot instance to build

func NewBot

func NewBot(name string, v *viper.Viper, options ...Option) (sb *Builder)

NewBot returns a new Builder used to set up a new slackscot

func (*Builder) Build

func (sb *Builder) Build() (s *Slackscot, err error)

Build returns the built slackscot instance. If there was an error during setup, the error is returned along with a nil slackscot

func (*Builder) WithConfigurablePluginCloserErr

func (sb *Builder) WithConfigurablePluginCloserErr(name string, newInstance CloserPluginInstantiator) *Builder

WithConfigurablePluginCloserErr adds a closer plugin to the slackscot instance by first checking and getting its configuration

func (*Builder) WithConfigurablePluginErr

func (sb *Builder) WithConfigurablePluginErr(name string, newInstance PluginInstantiator) *Builder

WithConfigurablePluginErr adds a plugin to the slackscot instance by first checking and getting its configuration

func (*Builder) WithPlugin

func (sb *Builder) WithPlugin(p *Plugin) *Builder

WithPlugin adds a plugin to the slackscot instance

func (*Builder) WithPluginCloserErr

func (sb *Builder) WithPluginCloserErr(closer io.Closer, p *Plugin, err error) *Builder

WithPluginCloserErr adds a plugin that has a creation function returning (io.Closer, Plugin, error) to the slackscot instance

func (*Builder) WithPluginErr

func (sb *Builder) WithPluginErr(p *Plugin, err error) *Builder

WithPluginErr adds a plugin that has a creation function returning (Plugin, error) to the slackscot instance

type CloserPluginInstantiator

type CloserPluginInstantiator func(c *config.PluginConfig) (closer io.Closer, p *Plugin, err error)

CloserPluginInstantiator creates a new instance of a closer plugin given a PluginConfig

type CommandMatcher added in v1.41.0

type CommandMatcher interface {
	// Return True if the message is a command
	IsCmd(msg slack.Msg) bool
	// Prefix to use with help text
	UsagePrefix() string
	// TrimPrefix is the cmdPrefix that should be removed from non-DM commands
	TrimPrefix(string) string
	fmt.Stringer
}

CommandMatcher is the interface that wraps methods required for defining how commands are matched and how the corresponding slack user interface is generated

type DefaultFileUploader

type DefaultFileUploader struct {
	// contains filtered or unexported fields
}

DefaultFileUploader holds a bare-bone SlackFileUploader

func NewFileUploader

func NewFileUploader(slackFileUploader SlackFileUploader) (fileUploader *DefaultFileUploader)

NewFileUploader returns a new DefaultFileUploader wrapping a FileUploader

func (*DefaultFileUploader) UploadFile

func (fileUploader *DefaultFileUploader) UploadFile(params slack.FileUploadParameters, options ...UploadOption) (file *slack.File, err error)

UploadFile uploads a file given the slack.FileUploadParameters with the UploadOptions applied to it

type EmojiReactor

type EmojiReactor interface {
	// AddReaction adds an emoji reaction to a ItemRef using the emoji associated
	// with the given name (i.e. name should be thumbsup rather than :thumbsup:)
	AddReaction(name string, item slack.ItemRef) error
}

EmojiReactor is implemented by any value that has the AddReaction method. The main purpose is a slight decoupling of the slack.Client in order for plugins to be able to write cleaner tests more easily

type EmojiReactorWithTelemetry added in v1.42.0

type EmojiReactorWithTelemetry struct {
	// contains filtered or unexported fields
}

EmojiReactorWithTelemetry implements EmojiReactor interface with all methods wrapped with open telemetry metrics

func NewEmojiReactorWithTelemetry added in v1.42.0

func NewEmojiReactorWithTelemetry(base EmojiReactor, name string, meter metric.Meter) EmojiReactorWithTelemetry

NewEmojiReactorWithTelemetry returns an instance of the EmojiReactor decorated with open telemetry timing and count metrics

func (EmojiReactorWithTelemetry) AddReaction added in v1.42.0

func (_d EmojiReactorWithTelemetry) AddReaction(name string, item slack.ItemRef) (err error)

AddReaction implements EmojiReactor

type FileUploader

type FileUploader interface {
	// UploadFile uploads a file to slack. For more info in this API, check
	// https://godoc.org/github.com/slack-go/slack#Client.UploadFile
	UploadFile(params slack.FileUploadParameters, options ...UploadOption) (file *slack.File, err error)
}

FileUploader is implemented by any value that has the UploadFile method. slack.Client *almost* implements it but requires a thin wrapping to do so to handle UploadOption there for added extensibility. The main purpose remains is a slight decoupling of the slack.Client in order for plugins to be able to write cleaner tests more easily.

type FileUploaderWithTelemetry added in v1.42.0

type FileUploaderWithTelemetry struct {
	// contains filtered or unexported fields
}

FileUploaderWithTelemetry implements FileUploader interface with all methods wrapped with open telemetry metrics

func NewFileUploaderWithTelemetry added in v1.42.0

func NewFileUploaderWithTelemetry(base FileUploader, name string, meter metric.Meter) FileUploaderWithTelemetry

NewFileUploaderWithTelemetry returns an instance of the FileUploader decorated with open telemetry timing and count metrics

func (FileUploaderWithTelemetry) UploadFile added in v1.42.0

func (_d FileUploaderWithTelemetry) UploadFile(params slack.FileUploadParameters, options ...UploadOption) (file *slack.File, err error)

UploadFile implements FileUploader

type IncomingMessage

type IncomingMessage struct {
	// The original slack.Msg text stripped from the "<@Mention>" cmdPrefix, if applicable
	NormalizedText string
	slack.Msg
}

IncomingMessage holds data for an incoming slack message. In addition to a slack.Msg, it also has a normalized text that is the original text stripped from the "<@Mention>" cmdPrefix when a message is addressed to a slackscot instance. Since commands are usually received either via direct message (without @Mention) or on channels with @Mention, the normalized text is useful there to allow plugins to have a single version to do Match and Answer against

type Matcher

type Matcher func(m *IncomingMessage) bool

Matcher is the function that determines whether or not an action should be triggered based on a IncomingMessage (which includes a slack.Msg and a normalized text content. Note that a match doesn't guarantee that the action should actually respond with anything once invoked

type Option

type Option func(*Slackscot)

Option defines an option for a Slackscot

func OptionCommandPrefix added in v1.41.0

func OptionCommandPrefix(cmdPrefix string) Option

OptionCommandPrefix sets a cmdPrefix to all commands that is used instead of at-mentioning the bot

func OptionLog

func OptionLog(logger *log.Logger) Option

OptionLog sets a logger for Slackscot

func OptionLogfile

func OptionLogfile(logfile *os.File) Option

OptionLogfile sets a logfile for Slackscot while using the other default logging cmdPrefix and options

func OptionNoPluginNamespacing

func OptionNoPluginNamespacing() Option

OptionNoPluginNamespacing disables plugin command namespacing for this instance. This means that namespacing plugin candidates will run without any extra plugin name matching required This is useful to simplify command usage for instances running a single plugin

func OptionTestMode

func OptionTestMode(terminationCh chan bool) Option

OptionTestMode sets the instance in test mode which instructs it to react to a goodbye event to terminate its execution. It is meant to be used for testing only and mostly in conjunction with github.com/slack-go/slack/slacktest. Very importantly, the termination message must be formed correctly so that the slackscot instance terminates correctly for tests to actually terminate.

Here's an example:

 testServer := slacktest.NewTestServer()
 testServer.Handle("/channels.create", slacktest.Websocket(func(conn *websocket.Conn) {
     // Trigger a termination on any API call to channels.create
	    slacktest.RTMServerSendGoodbye(conn)
 }))
 testServer.Start()
 defer testServer.Stop()

 termination := make(chan bool)
 s, err := New("BobbyTables", config.NewViperWithDefaults(), OptionWithSlackOption(slack.OptionAPIURL(testServer.GetAPIURL())), OptionTestMode(termination))
 require.NoError(t, err)

 tp := newTestPlugin()
 s.RegisterPlugin(tp)

 go s.Run()

 // TODO: Use the testserver to send events and messages and assert your plugin's behavior

 // Send this event to the testServer's websocket. This gets transformed into a
 // slack.DisconnectedEvent with Cause equal to slack.ErrRTMGoodbye that slackscot will
 // interpret as a signal to self-terminate
 testServer.SendToWebsocket("{\"type\":\"goodbye\"}")

 // Wait for slackscot to terminate
 <-termination

func OptionWithSlackOption

func OptionWithSlackOption(opt slack.Option) Option

OptionWithSlackOption adds a slack.Option to apply on the slack client

type OutgoingMessage

type OutgoingMessage struct {
	slack.OutgoingMessage

	// Answer from plugins/internal commands
	Answer
	// contains filtered or unexported fields
}

OutgoingMessage holds a plugin generated slack outgoing message along with the plugin identifier

type Plugin

type Plugin struct {
	Name string

	NamespaceCommands bool // Set to true for slackscot-managed namespacing of commands where the namespace/cmdPrefix to all commands is set to the plugin name

	Commands         []ActionDefinition
	HearActions      []ActionDefinition
	ScheduledActions []ScheduledActionDefinition

	// Those slackscot services are injected post-creation when slackscot is called.
	// A plugin shouldn't rely on those being available during creation
	UserInfoFinder    UserInfoFinder
	Logger            SLogger
	EmojiReactor      EmojiReactor
	FileUploader      FileUploader
	RealTimeMsgSender RealTimeMessageSender

	// The slack.Client is injected post-creation. It gives access to all the https://godoc.org/github.com/slack-go/slack#Client.
	// Plugin writers might want to check out https://godoc.org/github.com/slack-go/slack/slacktest to create a slack test server in order
	// to mock a slack server to test plugins using the SlackClient.
	SlackClient *slack.Client
}

Plugin represents a plugin (its name, action definitions and slackscot injected services)

Set NamespaceCommands to true if the plugin's commands are to be namespaced by slackscot. This means that commands will be first checked for a cmdPrefix that matches the plugin name. Since that's all handled by slackscot, a plugin should be written with matching only considering what comes after the namespace. For example, a plugin with name make would have a coffee command be something like

Match: func(m *IncomingMessage) bool {
    return strings.HasPrefix(m.NormalizedText, "coffee ")
},
Usage:       "coffee `<when>`",
Description: "Make coffee",
Answer: func(m *IncomingMessage) *Answer {
    when := strings.TrimPrefix(m.NormalizedText, "coffee ")
    return &Answer{Text: fmt.Sprintf("coffee will be reading %s", when))}}
}

In this example, if namespacing is enabled, a user would trigger the command with a message such as:

<@slackscotID> make coffee in 10 minutes

Note that the plugin itself doesn't need to concern itself with the namespace in the matching or answering as the NormalizedText has been formatted to be stripped of namespacing whether or not that's enabled and slackscot will have made sure the namespace matched if enabled.

At runtime, instances of slackscot can request to disregard namespacing with OptionNoPluginNamespacing (for example, to run a single plugin and simplify usage).

type PluginInstantiator

type PluginInstantiator func(c *config.PluginConfig) (p *Plugin, err error)

PluginInstantiator creates a new instance of a plugin given a PluginConfig

type RealTimeMessageSender

type RealTimeMessageSender interface {
	// NewOutgoingMessage is the function that creates a new message to send. See https://godoc.org/github.com/slack-go/slack#RTM.NewOutgoingMessage for more details
	NewOutgoingMessage(text string, channelID string, options ...slack.RTMsgOption) *slack.OutgoingMessage

	// SendMessage is the function that sends a new real time message. See https://godoc.org/github.com/slack-go/slack#RTM.SendMessage for more details
	SendMessage(outMsg *slack.OutgoingMessage)
}

RealTimeMessageSender is implemented by any value that has the NewOutgoingMessage method. The main purpose is a slight decoupling of the slack.RTM in order for plugins to be able to write tests more easily if all they do is send new messages on a channel

type SLogger

type SLogger interface {
	Printf(format string, v ...interface{})

	Debugf(format string, v ...interface{})
}

SLogger is the slackscot internal logging interface. The standard library logger implements this interface

type ScheduledAction

type ScheduledAction func()

ScheduledAction is what gets executed when a ScheduledActionDefinition is triggered (by its ScheduleDefinition) In order to do anything, a plugin should define its scheduled actions functions with itself as a receiver so the function has access to the injected services

type ScheduledActionDefinition

type ScheduledActionDefinition struct {
	// Indicates whether the action should be omitted from the help message
	Hidden bool

	// Schedule definition determining when the action runs
	Schedule schedule.Definition

	// Help description for the scheduled action
	Description string

	// ScheduledAction is the function that is invoked when the schedule activates
	Action ScheduledAction
}

ScheduledActionDefinition represents when a scheduled action is triggered as well as what it does and how

type SelfMatcher added in v1.41.0

type SelfMatcher interface {
	// IsBot returns true if the message is from the bot
	IsBot(msg slack.Msg) bool
	fmt.Stringer
}

SelfMatcher is the interface defining how slackscot determines that a message originates from itself

type SlackFileUploader

type SlackFileUploader interface {
	// UploadFile uploads a file to slack. For more info in this API, check
	// https://godoc.org/github.com/slack-go/slack#Client.UploadFile
	UploadFile(params slack.FileUploadParameters) (file *slack.File, err error)
}

SlackFileUploader is implemented by any value that has the UploadFile method. slack.Client implements it. The main purpose remains is a slight decoupling of the slack.Client in order for plugins to be able to write cleaner tests more easily.

type SlackMessageID

type SlackMessageID struct {
	// contains filtered or unexported fields
}

SlackMessageID holds the elements that form a unique message identifier for slack. Technically, slack also uses the workspace id as the first part of that unique identifier but since an instance of slackscot only lives within a single workspace, that part is left out

func (SlackMessageID) IsMsgModifiable

func (sid SlackMessageID) IsMsgModifiable() bool

IsMsgModifiable returns true if this slack message id can be used to update/delete the message. In practice, ephemeral messages don't have a channel ID and can't be deleted/updated so this would be a case where IsMsgModifiable would return false

func (SlackMessageID) String added in v1.38.0

func (sid SlackMessageID) String() string

String returns the string representation of a SlackMessageID

type Slackscot

type Slackscot struct {
	// contains filtered or unexported fields
}

Slackscot represents what defines a Slack Mascot (mostly, a name and its plugins)

func New

func New(name string, v *viper.Viper, options ...Option) (s *Slackscot, err error)

New creates a new slackscot from an array of plugins and a name

func NewSlackscot deprecated

func NewSlackscot(name string, v *viper.Viper, options ...Option) (s *Slackscot, err error)

NewSlackscot creates a new slackscot from an array of plugins and a name

Deprecated: Use New instead. Will be removed in 2.0.0

func (*Slackscot) Close

func (s *Slackscot) Close() (err error)

Close closes all closers of this slackscot. The first error that occurs during a Close is returned but regardless, all closers are attempted to be closed

func (*Slackscot) RegisterPlugin

func (s *Slackscot) RegisterPlugin(p *Plugin)

RegisterPlugin registers a plugin with the Slackscot engine. This should be invoked prior to calling Run

func (*Slackscot) Run

func (s *Slackscot) Run() (err error)

Run starts the Slackscot and loops until the process is interrupted

type UploadOption

type UploadOption func(params *slack.FileUploadParameters)

UploadOption defines an option on a FileUploadParameters (i.e. upload on thread)

func UploadInThreadOption

func UploadInThreadOption(m *IncomingMessage) UploadOption

UploadInThreadOption sets the file upload thread timestamp to an existing thread timestamp if the incoming message triggering this is on an existing thread

type UserInfoFinder

type UserInfoFinder interface {
	GetUserInfo(userID string) (user *slack.User, err error)
}

UserInfoFinder defines the interface for finding a slack user's info

func NewCachingUserInfoFinder

func NewCachingUserInfoFinder(v *viper.Viper, loader UserInfoFinder, logger SLogger) (uf UserInfoFinder, err error)

NewCachingUserInfoFinder creates a new user info service with caching if enabled via userProfileCacheSizeKey. It requires an implementation of the interface that will do the actual loading when not in cache

type UserInfoFinderWithTelemetry added in v1.42.0

type UserInfoFinderWithTelemetry struct {
	// contains filtered or unexported fields
}

UserInfoFinderWithTelemetry implements UserInfoFinder interface with all methods wrapped with open telemetry metrics

func NewUserInfoFinderWithTelemetry added in v1.42.0

func NewUserInfoFinderWithTelemetry(base UserInfoFinder, name string, meter metric.Meter) UserInfoFinderWithTelemetry

NewUserInfoFinderWithTelemetry returns an instance of the UserInfoFinder decorated with open telemetry timing and count metrics

func (UserInfoFinderWithTelemetry) GetUserInfo added in v1.42.0

func (_d UserInfoFinderWithTelemetry) GetUserInfo(userID string) (user *slack.User, err error)

GetUserInfo implements UserInfoFinder

Directories

Path Synopsis
Package actions provides a fluent API for creating slackscot plugin actions.
Package actions provides a fluent API for creating slackscot plugin actions.
Package config provides some utilities and structs to access configuration loaded via Viper
Package config provides some utilities and structs to access configuration loaded via Viper
Package plugin provides a fluent API for creating slackscot plugins.
Package plugin provides a fluent API for creating slackscot plugins.
Package plugins provides a collection of example (and usable) plugins for instances of slackscot Package plugins provides a collection of example (and usable) plugins for instances of slackscot
Package plugins provides a collection of example (and usable) plugins for instances of slackscot Package plugins provides a collection of example (and usable) plugins for instances of slackscot
Package schedule defines the interface for scheduling of slackscot actions
Package schedule defines the interface for scheduling of slackscot actions
Package store provides a simple and convenient data store interface for plugins to persist data along with a default filed-based leveldb implementation.
Package store provides a simple and convenient data store interface for plugins to persist data along with a default filed-based leveldb implementation.
datastoredb
Package datastoredb provides an implementation of github.com/alexandre-normand/slackscot/store's StringStorer interface backed by the Google Cloud Datastore.
Package datastoredb provides an implementation of github.com/alexandre-normand/slackscot/store's StringStorer interface backed by the Google Cloud Datastore.
inmemorydb
Package inmemorydb provides an implementation of github.com/alexandre-normand/slackscot/store's StringStorer interface as an in-memory data store relying on a wrapping StringStorer for actual persistence.
Package inmemorydb provides an implementation of github.com/alexandre-normand/slackscot/store's StringStorer interface as an in-memory data store relying on a wrapping StringStorer for actual persistence.
mocks
Package mocks contains a mock of the store package interfaces
Package mocks contains a mock of the store package interfaces
Package test provides testing utilities for users of slackscot to help writing tests for their plugins and extensions
Package test provides testing utilities for users of slackscot to help writing tests for their plugins and extensions
assertanswer
Package assertanswer provides testing functions to validate a plugin's answer This package is most useful when used in combination with github.com/alexandre-normand/slackscot/test/assertplugin but can be used alone to test individual slackscot Actions.
Package assertanswer provides testing functions to validate a plugin's answer This package is most useful when used in combination with github.com/alexandre-normand/slackscot/test/assertplugin but can be used alone to test individual slackscot Actions.
assertplugin
Package assertplugin provides testing functions to validate a plugin's overall functionality.
Package assertplugin provides testing functions to validate a plugin's overall functionality.
capture
Package capture provides dummy implementations of some of the plugin services that capture data fed into them in order to allow validation in tests
Package capture provides dummy implementations of some of the plugin services that capture data fed into them in order to allow validation in tests

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL