marvin

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

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

Go to latest
Published: Dec 18, 2019 License: GPL-3.0 Imports: 17 Imported by: 0

README

Marvin

Slack bot that uses the RTM API to interact with users using normal messages instead of slash commands.

Code Structure

Typedefs are in interfaces.go and slack/.

The websocket connection code is in slack/rtm; the implementation of the Team type is the slack/controller package.

main() lives in cmd/slacktest. Some brief database infrastructure is in database/.

Most of the functionality lives in modules/. The atcommand module handles command parsing. The factoid module handles information storage/retrieval via factoids.

License

Marvin is available of the terms of the GPLv3, or any later version at your discretion.

Documentation

Index

Constants

View Source
const (
	ReplyTypeInvalid      ReplyType = 0
	ReplyTypeShortProblem           = ReplyTypeInChannel | ReplyTypeLog
	ReplyTypeLongProblem            = ReplyTypePM | ReplyTypeLog
	ReplyTypeDestinations           = ReplyTypeInChannel | ReplyTypePM | ReplyTypeLog
)
View Source
const LongReplyCut = 100
View Source
const LongReplyThreshold = 400
View Source
const ShortReplyThreshold = 35
View Source
const UndoCustom = util.TriYes
View Source
const UndoSimple = 2

Variables

This section is empty.

Functions

func RegisterModule

func RegisterModule(c ModuleConstructor)

RegisterModule should be called during package init() and stores the constructor for a module.

Types

type AccessLevel

type AccessLevel int

AccessLevel represents the level of rights a user has.

const (
	AccessLevelInvalid AccessLevel = iota
	AccessLevelBlacklisted
	AccessLevelNormal
	AccessLevelChannelAdmin
	AccessLevelAdmin
	AccessLevelController
)

type ActionSource

type ActionSource interface {
	UserID() slack.UserID
	ChannelID() slack.ChannelID
	MsgTimestamp() slack.MessageTS
	AccessLevel() AccessLevel
	ArchiveLink() string
}

ActionSource represents the cause of actions or commands.

type ActionSourceUserMessage

type ActionSourceUserMessage struct {
	Team Team
	Msg  slack.SlackTextMessage
}

func (ActionSourceUserMessage) AccessLevel

func (um ActionSourceUserMessage) AccessLevel() AccessLevel
func (um ActionSourceUserMessage) ArchiveLink() string

func (ActionSourceUserMessage) ChannelID

func (um ActionSourceUserMessage) ChannelID() slack.ChannelID

func (ActionSourceUserMessage) MsgTimestamp

func (um ActionSourceUserMessage) MsgTimestamp() slack.MessageTS

func (ActionSourceUserMessage) UserID

func (um ActionSourceUserMessage) UserID() slack.UserID

type CommandArguments

type CommandArguments struct {
	Source            ActionSource
	Command           string
	Arguments         []string
	OriginalArguments []string
	Ctx               context.Context

	IsEdit         bool
	IsUndo         bool
	PreviousResult *CommandResult
	ModuleData     interface{}
}

CommandArguments contains the post-split arguments arrays. The pre-split string is not available, as the arguments can sometimes come in as an array.

func (*CommandArguments) Pop

func (args *CommandArguments) Pop() string

Pop moves the first element of Arguments to Command and returns the new value of Command.

func (*CommandArguments) PreArgs

func (args *CommandArguments) PreArgs() []string

PreArgs returns the slice of all arguments that have been Pop()ped.

func (*CommandArguments) SetModuleData

func (args *CommandArguments) SetModuleData(v interface{})

SetModuleData is useful for editing.

type CommandRegistration

type CommandRegistration interface {
	RegisterCommand(name string, c SubCommand)
	RegisterCommandFunc(name string, c SubCommandFunc, help string) SubCommand
	UnregisterCommand(name string)
}

type CommandResult

type CommandResult struct {
	Args      *CommandArguments
	Message   string
	Err       error
	Code      CommandResultCode
	ReplyType ReplyType
	Sent      bool

	CanEdit util.TriValue
	CanUndo util.TriValue
}

A CommandResult is the return of a command. Use the Cmd* constructors to make them. If you want to specify where the reply will go, call WithReplyType().

CommandResult objects are to be passed and modified by-value.

A panicking command is converted into an Err-containing CommandResult.

func CmdError

func CmdError(args *CommandArguments, err error, msg string) CommandResult

CmdError includes the Err field for the CmdResultError code. An error is something that shouldn't normally happen - access violations go under Failure.

func CmdFailuref

func CmdFailuref(args *CommandArguments, format string, v ...interface{}) CommandResult

CmdFailuref formats a string to create a CmdResultFailure result.

func CmdHelpf

func CmdHelpf(args *CommandArguments, format string, v ...interface{}) CommandResult

CmdHelpf formats a string to create a CmdResultPrintHelp result.

func CmdSuccess

func CmdSuccess(args *CommandArguments, msg string) CommandResult

CmdSuccess simply takes a message to give to the user for an OK result.

func CmdUsage

func CmdUsage(args *CommandArguments, usage string) CommandResult

CmdUsage takes the usage string for a CmdResultPrintUsage result.

func (CommandResult) WithCustomUndo

func (r CommandResult) WithCustomUndo() CommandResult

func (CommandResult) WithEdit

func (r CommandResult) WithEdit() CommandResult

func (CommandResult) WithNoEdit

func (r CommandResult) WithNoEdit() CommandResult

func (CommandResult) WithNoUndo

func (r CommandResult) WithNoUndo() CommandResult

func (CommandResult) WithReplyType

func (r CommandResult) WithReplyType(rt ReplyType) CommandResult

WithReplyType explicitly sets where the response should be directed.

The caller can override this if desired, but it will be respected for all commands initiated directly by users.

func (CommandResult) WithSimpleUndo

func (r CommandResult) WithSimpleUndo() CommandResult

type CommandResultCode

type CommandResultCode int
const (
	CmdResultOK CommandResultCode = iota
	CmdResultFailure
	CmdResultError
	CmdResultNoSuchCommand
	CmdResultPrintUsage
	CmdResultPrintHelp
)

type ErrConfNoDefault

type ErrConfNoDefault struct {
	Key string
}

ErrConfNoDefault is an error return from ModuleConfig.GetIsDefault.

func (ErrConfNoDefault) Error

func (e ErrConfNoDefault) Error() string

Error implements the error interface.

type ErrConfProtected

type ErrConfProtected struct{ Key string }

ErrConfProtected is an error return from ModuleConfig.GetIsDefaultNotProtected.

func (ErrConfProtected) Error

func (e ErrConfProtected) Error() string

Error implements the error interface.

type HTTPDoer

type HTTPDoer interface {
	Do(*http.Request) (*http.Response, error)
}

type HasTeam

type HasTeam interface {
	Team() Team
}

HasTeam is a type that references a marvin.Team.

type Module

type Module interface {
	// Modules should declare a constant named 'Identifier' in their package
	// and return it from this function.
	Identifier() ModuleID

	// Load should declare dependencies.
	Load(t Team)
	// Enable has dependencies available.
	Enable(t Team)
	// Disable should shut down and unregister all resources.
	Disable(t Team)
}

type ModuleConfig

type ModuleConfig interface {
	// Get gets a module configuration value.  The error will be set on
	// database errors.  Get() will panic if the key was not initialized with
	// Add() or AddProtect().
	Get(key string) (string, error)
	// GetIsDefault gets a module configuration value, but does not require the
	// key have been initialized.
	//
	// 1) If the key was not initialized with Add(), value is the empty string,
	// isDefault is true, and err is ErrConfNoDefault.
	//
	// 2) If the key was initialized, but has no override, value is the default
	// value, isDefault is true, and err is nil.
	//
	// 3) If the key has an override, value is the override, isDefault is
	// false, and err is nil.
	GetIsDefault(key string) (value string, isDefault bool, err error)
	// GetIsDefaultNotProtected acts like GetIsDefault, but returns ("", false,
	// ErrConfProtected) if the key is protected.
	GetIsDefaultNotProtected(key string) (value string, isDefault bool, err error)
	// Set sets an override for the given configuration key.
	Set(key, value string) error
	// SetDefault resets the configuration for the given key to the default.
	SetDefault(key string) error
	// Add initializes the default value for a key for use with Get().  This
	// must be called during the module Load phase.
	Add(key, defaultValue string)
	// Add initializes the default value for a key for use with Get(), and also
	// sets the key as protected.  This must be called during the module Load
	// phase.
	AddProtect(key, defaultValue string, protect bool)
	// OnModify registers a callback for when a key is modified.
	OnModify(f func(key string))

	// ListDefaults returns the defaults map.  This cannot be called during the
	// module Load phase.
	ListDefaults() map[string]string
	// ListDefaults returns the protected-keys map.  This cannot be called
	// during the module Load phase.
	ListProtected() map[string]bool
}

type ModuleConstructor

type ModuleConstructor func(team Team) Module

ModuleConstructor is the type of the function that init() must pass to RegisterModule.

func AllModules

func AllModules() []ModuleConstructor

AllModules returns all constructors given to RegisterModule().

type ModuleID

type ModuleID string

ModuleID is a string constant identifying a module.

type ModuleState

type ModuleState int

ModuleState is an enum representing the state of a module.

const (
	ModuleStateConstructed ModuleState
	ModuleStateLoaded
	ModuleStateEnabled
	ModuleStateDisabled
	ModuleStateErrorLoading
	ModuleStateErrorEnabling
)

type ModuleStatus

type ModuleStatus interface {
	Instance() Module
	State() ModuleState
	// Returns non-nil if Degraded returns true.
	Err() error

	IsLoaded() bool
	IsEnabled() bool
	Degraded() bool
}

type ParentCommand

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

func NewParentCommand

func NewParentCommand() *ParentCommand

func (*ParentCommand) Handle

func (pc *ParentCommand) Handle(t Team, args *CommandArguments) CommandResult

func (*ParentCommand) Help

func (pc *ParentCommand) Help(t Team, args *CommandArguments) CommandResult

func (*ParentCommand) RegisterCommand

func (pc *ParentCommand) RegisterCommand(name string, c SubCommand)

func (*ParentCommand) RegisterCommandFunc

func (pc *ParentCommand) RegisterCommandFunc(name string, f SubCommandFunc, help string) SubCommand

func (*ParentCommand) UnregisterCommand

func (pc *ParentCommand) UnregisterCommand(name string)

func (*ParentCommand) WithHelp

func (pc *ParentCommand) WithHelp(extraHelp string) *ParentCommand

type ReplyType

type ReplyType int
const (
	ReplyTypePM ReplyType = 1 << iota
	ReplyTypeInChannel
	ReplyTypeLog
	ReplyTypeFlagOmitUsername
)

type SendMessage

type SendMessage interface {
	SendMessage(channelID slack.ChannelID, message string) (slack.MessageTS, slack.RTMRawMessage, error)
	SendComplexMessage(channelID slack.ChannelID, message slack.OutgoingSlackMessage) (slack.MessageTS, slack.RTMRawMessage, error)
}

type SubCommand

type SubCommand interface {
	Handle(t Team, args *CommandArguments) CommandResult
	Help(t Team, args *CommandArguments) CommandResult
}

type SubCommandFunc

type SubCommandFunc func(t Team, args *CommandArguments) CommandResult

type Team

type Team interface {
	// Domain returns the leftmost component of the Slack domain name.
	Domain() string
	DB() *database.Conn
	TeamConfig() *TeamConfig
	ModuleConfig(mod ModuleID) ModuleConfig
	// ModuleConfigList returns a list of all ModuleIDs with configs
	ModuleConfigList() []ModuleID

	// BotUser returns the user ID that Marvin is signed in as.
	BotUser() slack.UserID
	// TeamID returns the Slack Team ID of the connected Slack team.
	TeamID() slack.TeamID

	// EnableModules loads every module and attempts to transition them to
	// the state listed in the configuration.
	EnableModules() bool
	Shutdown()

	// DependModule places the instance of the requested module in the given
	// pointer.
	//
	// If the requested module is already enabled, the pointer is filled
	// immediately and the function returns 1. If the requested module has
	// errored, the pointer is left alone and the function returns -2.
	//
	// During loading, when the requested module has not been enabled yet, the
	// function returns 0 and remembers the pointer. If the requested module is
	// not known, the function returns -1.
	DependModule(self Module, dependencyID ModuleID, ptr *Module) int
	// GetModule returns the Module instance for a module directly.
	GetModule(modID ModuleID) Module
	GetModuleStatus(modID ModuleID) ModuleStatus
	// GetAllModules() returns the status of all modules.
	GetAllModules() []ModuleStatus
	// GetAllModules() returns the status of all enabled modules.
	GetAllEnabledModules() []ModuleStatus

	SendMessage
	ReactMessage(msgID slack.MessageID, emojiName string) error

	// SlackAPIPost makes a Slack API call by adding the token to the form.  If
	// the token parameter is already defined, the existing value is used.
	SlackAPIPostRaw(method string, form url.Values) (*http.Response, error)
	SlackAPIPostJSON(method string, form url.Values, result interface{}) error

	ArchiveURL(msgID slack.MessageID) string

	OnEveryEvent(mod ModuleID, f func(slack.RTMRawMessage))
	OnEvent(mod ModuleID, event string, f func(slack.RTMRawMessage))
	OnNormalMessage(mod ModuleID, f func(slack.RTMRawMessage))
	OnSpecialMessage(mod ModuleID, msgSubtype []string, f func(slack.RTMRawMessage))
	OffAllEvents(mod ModuleID)
	GetRTMClient() interface{}

	CommandRegistration
	DispatchCommand(args *CommandArguments) CommandResult

	// Add a new HTTP route handler.
	HandleHTTP(path string, handler http.Handler) *mux.Route
	// Get the Router object to add new routes.
	Router() *mux.Router
	// Inject middleware for every HTTP request. This is processed before CSRF
	// protection.
	HTTPMiddleware(f func(http.Handler) http.Handler)
	// Resolve a relative path to an absolute URL, taking into account
	// subfolder configuration.
	AbsoluteURL(path string) string

	ReportError(err error, source ActionSource)

	ResolveChannelName(input string) slack.ChannelID
	ChannelName(channel slack.ChannelID) string
	FormatChannel(channel slack.ChannelID) string
	ResolveUserName(input string) slack.UserID
	UserName(user slack.UserID) string
	UserLevel(user slack.UserID) AccessLevel
	GetIM(user slack.UserID) (slack.ChannelID, error)
	GetIMOtherUser(channel slack.ChannelID) (slack.UserID, error)
	PublicChannelInfo(channel slack.ChannelID) (*slack.Channel, error)
	PrivateChannelInfo(channel slack.ChannelID) (*slack.Channel, error)
	ChannelIDByName(chName string) slack.ChannelID
	ChannelMemberCount(channel slack.ChannelID) int
	ChannelMemberList(channel slack.ChannelID) []slack.UserID
	UserInfo(user slack.UserID) (*slack.User, error)
	// Only supports private channels
	UserInChannels(user slack.UserID, channels ...slack.ChannelID) map[slack.ChannelID]bool
}

Team represents a Slack team, and is the "god object" for Marvin.

Its implementation is in the marvin/slack/controller package.

type TeamConfig

type TeamConfig struct {
	TeamDomain      string
	ClientID        string
	ClientSecret    string
	CookieSecretKey string
	IntraUID        string
	IntraSecret     string
	DatabaseURL     string
	UserToken       string
	LogChannel      slack.ChannelID
	HTTPListen      string
	HTTPURL         string
	Controllers     []slack.UserID
	ChannelPrefix   slack.ChannelID
	IsSlackAdmin    bool
	IsDevelopment   bool
	IsReadOnly      bool
}

TeamConfig is loaded from the config.ini file.

func LoadTeamConfig

func LoadTeamConfig(sec *ini.Section) *TeamConfig

func (*TeamConfig) CheckChannelName

func (t *TeamConfig) CheckChannelName(chanName string) bool

This checks if the channel where a factoid / command invocation is coming from one of our own channels. It will ignore the message otherwise.

func (*TeamConfig) GetSecretKey

func (t *TeamConfig) GetSecretKey(purpose string, p []byte) (n int, err error)

GetSecretKey expands the CookieSecretKey value using the 'purpose' parameter as a salt. An example value for 'purpose' would be "csrf protection".

func (*TeamConfig) IsController

func (t *TeamConfig) IsController(user slack.UserID) bool

Directories

Path Synopsis
cmd
The modules package contains all of Marvin's modules.
The modules package contains all of Marvin's modules.
_all
This package does nothing other than import all other module packages.
This package does nothing other than import all other module packages.
on_reaction
on_reaction provides a public cross-module interface.
on_reaction provides a public cross-module interface.
rss
Package pb is a generated protocol buffer package.
Package pb is a generated protocol buffer package.
controller
The controller package implements the Team type.
The controller package implements the Team type.
rtm

Jump to

Keyboard shortcuts

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