slacker

package module
v0.0.0-...-e8c034b Latest Latest
Warning

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

Go to latest
Published: Mar 3, 2019 License: MIT Imports: 7 Imported by: 0

README

slacker Go Report Card GoDoc

Built on top of the Slack API github.com/nlopes/slack with the idea to simplify the Real-Time Messaging feature to easily create Slack Bots, assign commands to them and extract parameters.

Features

  • Easy definitions of commands and their input
  • Available bot initialization, errors and default handlers
  • Simple parsing of String, Integer, Float and Boolean parameters
  • Contains support for context.Context
  • Built-in help command
  • Supports authorization
  • Bot responds to mentions and direct messages
  • Handlers run concurrently via goroutines
  • Full access to the Slack API github.com/nlopes/slack

Dependencies

Examples

Example 1

Defining a command using slacker

package main

import (
	"context"
	"github.com/shomali11/slacker"
	"log"
)

func main() {
	bot := slacker.NewClient("<YOUR SLACK BOT TOKEN>")

	definition := &slacker.CommandDefinition{
		Handler: func(request slacker.Request, response slacker.ResponseWriter) {
			response.Reply("pong")
		},
	}

	bot.Command("ping", definition)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := bot.Listen(ctx)
	if err != nil {
		log.Fatal(err)
	}
}

Example 2

Defining a command with an optional description and example

package main

import (
	"context"
	"github.com/shomali11/slacker"
	"log"
)

func main() {
	bot := slacker.NewClient("<YOUR SLACK BOT TOKEN>")

	definition := &slacker.CommandDefinition{
		Description: "Ping!",
		Example:     "ping",
		Handler: func(request slacker.Request, response slacker.ResponseWriter) {
			response.Reply("pong")
		},
	}

	bot.Command("ping", definition)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := bot.Listen(ctx)
	if err != nil {
		log.Fatal(err)
	}
}

Example 3

Defining a command with a parameter

package main

import (
	"context"
	"github.com/shomali11/slacker"
	"log"
)

func main() {
	bot := slacker.NewClient("<YOUR SLACK BOT TOKEN>")

	definition := &slacker.CommandDefinition{
		Description: "Echo a word!",
		Example:     "echo hello",
		Handler: func(request slacker.Request, response slacker.ResponseWriter) {
			word := request.Param("word")
			response.Reply(word)
		},
	}

	bot.Command("echo <word>", definition)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := bot.Listen(ctx)
	if err != nil {
		log.Fatal(err)
	}
}

Example 4

Defining a command with two parameters. Parsing one as a string and the other as an integer. (The second parameter is the default value in case no parameter was passed or could not parse the value)

package main

import (
	"context"
	"github.com/shomali11/slacker"
	"log"
)

func main() {
	bot := slacker.NewClient("<YOUR SLACK BOT TOKEN>")

	definition := &slacker.CommandDefinition{
		Description: "Repeat a word a number of times!",
		Example:     "repeat hello 10",
		Handler: func(request slacker.Request, response slacker.ResponseWriter) {
			word := request.StringParam("word", "Hello!")
			number := request.IntegerParam("number", 1)
			for i := 0; i < number; i++ {
				response.Reply(word)
			}
		},
	}

	bot.Command("repeat <word> <number>", definition)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := bot.Listen(ctx)
	if err != nil {
		log.Fatal(err)
	}
}

Example 5

Send an error message to the Slack channel

package main

import (
	"context"
	"errors"
	"github.com/shomali11/slacker"
	"log"
)

func main() {
	bot := slacker.NewClient("<YOUR SLACK BOT TOKEN>")

	definition := &slacker.CommandDefinition{
		Description: "Tests errors",
		Handler: func(request slacker.Request, response slacker.ResponseWriter) {
			response.ReportError(errors.New("Oops!"))
		},
	}

	bot.Command("test", definition)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := bot.Listen(ctx)
	if err != nil {
		log.Fatal(err)
	}
}

Example 6

Send a "Typing" indicator

package main

import (
	"context"
	"github.com/shomali11/slacker"
	"log"
	"time"
)

func main() {
	bot := slacker.NewClient("<YOUR SLACK BOT TOKEN>")

	definition := &slacker.CommandDefinition{
		Description: "Server time!",
		Handler: func(request slacker.Request, response slacker.ResponseWriter) {
			response.Typing()

			time.Sleep(time.Second)

			response.Reply(time.Now().Format(time.RFC1123))
		},
	}

	bot.Command("time", definition)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := bot.Listen(ctx)
	if err != nil {
		log.Fatal(err)
	}
}

Example 7

Showcasing the ability to access the github.com/nlopes/slack API and the Real-Time Messaging Protocol. In this example, we are sending a message using RTM and uploading a file using the Slack API.

package main

import (
	"context"
	"github.com/nlopes/slack"
	"github.com/shomali11/slacker"
	"log"
)

func main() {
	bot := slacker.NewClient("<YOUR SLACK BOT TOKEN>")

	definition := &slacker.CommandDefinition{
		Description: "Upload a word!",
		Handler: func(request slacker.Request, response slacker.ResponseWriter) {
			word := request.Param("word")
			channel := request.Event().Channel

			rtm := response.RTM()
			client := response.Client()

			rtm.SendMessage(rtm.NewOutgoingMessage("Uploading file ...", channel))
			client.UploadFile(slack.FileUploadParameters{Content: word, Channels: []string{channel}})
		},
	}

	bot.Command("upload <word>", definition)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := bot.Listen(ctx)
	if err != nil {
		log.Fatal(err)
	}
}

Example 8

Showcasing the ability to leverage context.Context to add a timeout

package main

import (
	"context"
	"errors"
	"github.com/shomali11/slacker"
	"log"
	"time"
)

func main() {
	bot := slacker.NewClient("<YOUR SLACK BOT TOKEN>")

	definition := &slacker.CommandDefinition{
		Description: "Process!",
		Handler: func(request slacker.Request, response slacker.ResponseWriter) {
			timedContext, cancel := context.WithTimeout(request.Context(), time.Second)
			defer cancel()

			select {
			case <-timedContext.Done():
				response.ReportError(errors.New("timed out"))
			case <-time.After(time.Minute):
				response.Reply("Processing done!")
			}
		},
	}

	bot.Command("process", definition)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := bot.Listen(ctx)
	if err != nil {
		log.Fatal(err)
	}
}

Example 9

Showcasing the ability to add attachments to a Reply

package main

import (
	"context"
	"log"

	"github.com/nlopes/slack"
	"github.com/shomali11/slacker"
)

func main() {
	bot := slacker.NewClient("<YOUR SLACK BOT TOKEN>")

	definition := &slacker.CommandDefinition{
		Description: "Echo a word!",
		Handler: func(request slacker.Request, response slacker.ResponseWriter) {
			word := request.Param("word")

			attachments := []slack.Attachment{}
			attachments = append(attachments, slack.Attachment{
				Color:      "red",
				AuthorName: "Raed Shomali",
				Title:      "Attachment Title",
				Text:       "Attachment Text",
			})

			response.Reply(word, slacker.WithAttachments(attachments))
		},
	}

	bot.Command("echo <word>", definition)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := bot.Listen(ctx)
	if err != nil {
		log.Fatal(err)
	}
}

Example 10

Showcasing the ability to create custom responses via CustomResponse

package main

import (
	"log"

	"context"
	"errors"
	"fmt"
	"github.com/nlopes/slack"
	"github.com/shomali11/slacker"
)

const (
	errorFormat = "> Custom Error: _%s_"
)

func main() {
	bot := slacker.NewClient("<YOUR SLACK BOT TOKEN>")

	bot.CustomResponse(NewCustomResponseWriter)

	definition := &slacker.CommandDefinition{
		Description: "Custom!",
		Handler: func(request slacker.Request, response slacker.ResponseWriter) {
			response.Reply("custom")
			response.ReportError(errors.New("oops"))
		},
	}

	bot.Command("custom", definition)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := bot.Listen(ctx)
	if err != nil {
		log.Fatal(err)
	}
}

// NewCustomResponseWriter creates a new ResponseWriter structure
func NewCustomResponseWriter(channel string, client *slack.Client, rtm *slack.RTM) slacker.ResponseWriter {
	return &MyCustomResponseWriter{channel: channel, client: client, rtm: rtm}
}

// MyCustomResponseWriter a custom response writer
type MyCustomResponseWriter struct {
	channel string
	client  *slack.Client
	rtm     *slack.RTM
}

// ReportError sends back a formatted error message to the channel where we received the event from
func (r *MyCustomResponseWriter) ReportError(err error) {
	r.rtm.SendMessage(r.rtm.NewOutgoingMessage(fmt.Sprintf(errorFormat, err.Error()), r.channel))
}

// Typing send a typing indicator
func (r *MyCustomResponseWriter) Typing() {
	r.rtm.SendMessage(r.rtm.NewTypingMessage(r.channel))
}

// Reply send a attachments to the current channel with a message
func (r *MyCustomResponseWriter) Reply(message string, options ...slacker.ReplyOption) {
	r.rtm.SendMessage(r.rtm.NewOutgoingMessage(message, r.channel))
}

// RTM returns the RTM client
func (r *MyCustomResponseWriter) RTM() *slack.RTM {
	return r.rtm
}

// Client returns the slack client
func (r *MyCustomResponseWriter) Client() *slack.Client {
	return r.client
}

Example 11

Showcasing the ability to toggle the slack Debug option via WithDebug

package main

import (
	"context"
	"github.com/shomali11/slacker"
	"log"
)

func main() {
	bot := slacker.NewClient("<YOUR SLACK BOT TOKEN>", slacker.WithDebug(true))

	definition := &slacker.CommandDefinition{
		Description: "Ping!",
		Handler: func(request slacker.Request, response slacker.ResponseWriter) {
			response.Reply("pong")
		},
	}

	bot.Command("ping", definition)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := bot.Listen(ctx)
	if err != nil {
		log.Fatal(err)
	}
}

Example 12

Defining a command that can only be executed by authorized users

package main

import (
	"context"
	"github.com/shomali11/slacker"
	"log"
)

func main() {
	bot := slacker.NewClient("<YOUR SLACK BOT TOKEN>")

	authorizedUsers := []string{"<USER ID>"}

	authorizedDefinition := &slacker.CommandDefinition{
		Description: "Very secret stuff",
		AuthorizationFunc: func(request slacker.Request) bool {
			return contains(authorizedUsers, request.Event().User)
		},
		Handler: func(request slacker.Request, response slacker.ResponseWriter) {
			response.Reply("You are authorized!")
		},
	}

	bot.Command("secret", authorizedDefinition)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := bot.Listen(ctx)
	if err != nil {
		log.Fatal(err)
	}
}

func contains(list []string, element string) bool {
	for _, value := range list {
		if value == element {
			return true
		}
	}
	return false
}

Example 13

Adding handlers to when the bot is connected, encounters an error and a default for when none of the commands match

package main

import (
	"log"

	"context"
	"fmt"
	"github.com/shomali11/slacker"
)

func main() {
	bot := slacker.NewClient("<YOUR SLACK BOT TOKEN>")

	bot.Init(func() {
		log.Println("Connected!")
	})

	bot.Err(func(err string) {
		log.Println(err)
	})

	bot.DefaultCommand(func(request slacker.Request, response slacker.ResponseWriter) {
		response.Reply("Say what?")
	})

	bot.DefaultEvent(func(event interface{}) {
		fmt.Println(event)
	})

	definition := &slacker.CommandDefinition{
		Description: "help!",
		Handler: func(request slacker.Request, response slacker.ResponseWriter) {
			response.Reply("Your own help function...")
		},
	}

	bot.Help(definition)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := bot.Listen(ctx)
	if err != nil {
		log.Fatal(err)
	}
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BotCommand

type BotCommand interface {
	Usage() string
	Definition() *CommandDefinition

	Match(text string) (*proper.Properties, bool)
	Tokenize() []*commander.Token
	Execute(request Request, response ResponseWriter)
}

BotCommand interface

func NewBotCommand

func NewBotCommand(usage string, definition *CommandDefinition) BotCommand

NewBotCommand creates a new bot command object

type ClientDefaults

type ClientDefaults struct {
	Debug bool
}

ClientDefaults configuration

type ClientOption

type ClientOption func(*ClientDefaults)

ClientOption an option for client values

func WithDebug

func WithDebug(debug bool) ClientOption

WithDebug sets debug toggle

type CommandDefinition

type CommandDefinition struct {
	Description       string
	Example           string
	AuthorizationFunc func(request Request) bool
	Handler           func(request Request, response ResponseWriter)
}

CommandDefinition structure contains definition of the bot command

type ReplyDefaults

type ReplyDefaults struct {
	Attachments []slack.Attachment
}

ReplyDefaults configuration

type ReplyOption

type ReplyOption func(*ReplyDefaults)

ReplyOption an option for reply values

func WithAttachments

func WithAttachments(attachments []slack.Attachment) ReplyOption

WithAttachments sets message attachments

type Request

type Request interface {
	Param(key string) string
	StringParam(key string, defaultValue string) string
	BooleanParam(key string, defaultValue bool) bool
	IntegerParam(key string, defaultValue int) int
	FloatParam(key string, defaultValue float64) float64
	Context() context.Context
	Event() *slack.MessageEvent
	Properties() *proper.Properties
}

Request interface that contains the Event received and parameters

func NewRequest

func NewRequest(ctx context.Context, event *slack.MessageEvent, properties *proper.Properties) Request

NewRequest creates a new Request structure

type ResponseWriter

type ResponseWriter interface {
	Reply(text string, options ...ReplyOption)
	ReportError(err error)
	Typing()
	RTM() *slack.RTM
	Client() *slack.Client
}

A ResponseWriter interface is used to respond to an event

func NewResponse

func NewResponse(channel string, client *slack.Client, rtm *slack.RTM) ResponseWriter

NewResponse creates a new response structure

type Slacker

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

Slacker contains the Slack API, botCommands, and handlers

func NewClient

func NewClient(token string, options ...ClientOption) *Slacker

NewClient creates a new client using the Slack API

func (*Slacker) BotCommands

func (s *Slacker) BotCommands() []BotCommand

BotCommands returns Bot Commands

func (*Slacker) Client

func (s *Slacker) Client() *slack.Client

Client returns the internal slack.Client of Slacker struct

func (*Slacker) Command

func (s *Slacker) Command(usage string, definition *CommandDefinition)

Command define a new command and append it to the list of existing commands

func (*Slacker) CustomRequest

func (s *Slacker) CustomRequest(requestConstructor func(ctx context.Context, event *slack.MessageEvent, properties *proper.Properties) Request)

CustomRequest creates a new request

func (*Slacker) CustomResponse

func (s *Slacker) CustomResponse(responseConstructor func(channel string, client *slack.Client, rtm *slack.RTM) ResponseWriter)

CustomResponse creates a new response writer

func (*Slacker) DefaultCommand

func (s *Slacker) DefaultCommand(defaultMessageHandler func(request Request, response ResponseWriter))

DefaultCommand handle messages when none of the commands are matched

func (*Slacker) DefaultEvent

func (s *Slacker) DefaultEvent(defaultEventHandler func(interface{}))

DefaultEvent handle events when an unknown event is seen

func (*Slacker) Err

func (s *Slacker) Err(errorHandler func(err string))

Err handle when errors are encountered

func (*Slacker) GetUserInfo

func (s *Slacker) GetUserInfo(user string) (*slack.User, error)

GetUserInfo retrieve complete user information

func (*Slacker) Help

func (s *Slacker) Help(definition *CommandDefinition)

Help handle the help message, it will use the default if not set

func (*Slacker) Init

func (s *Slacker) Init(initHandler func())

Init handle the event when the bot is first connected

func (*Slacker) Listen

func (s *Slacker) Listen(ctx context.Context) error

Listen receives events from Slack and each is handled as needed

func (*Slacker) RTM

func (s *Slacker) RTM() *slack.RTM

RTM returns returns the internal slack.RTM of Slacker struct

func (*Slacker) UnAuthorizedError

func (s *Slacker) UnAuthorizedError(unAuthorizedError error)

UnAuthorizedError error message

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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