Helperbot V2
This commit is contained in:
36
models/interfaces.go
Normal file
36
models/interfaces.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
)
|
||||
|
||||
type Model interface {
|
||||
SendMessage(msg BotMessage)
|
||||
GetSlackAdminDMId() string
|
||||
GetSlackLatency() time.Duration
|
||||
GetBytes(path []string) ([]byte, error)
|
||||
SetBytes(path []string, val []byte) error
|
||||
GetString(path []string) (string, error)
|
||||
SetString(path []string, val string) error
|
||||
GetInt(path []string) (int, error)
|
||||
SetInt(path []string, val int) error
|
||||
}
|
||||
|
||||
type Message interface {
|
||||
GetSource() string
|
||||
GetDestination() string
|
||||
GetType() string
|
||||
GetTarget() string
|
||||
GetText() string
|
||||
}
|
||||
|
||||
type PluginState interface {
|
||||
Name() string
|
||||
Initialize(Model) error
|
||||
ProcessMessage(BotMessage) bool
|
||||
ProcessRTMEvent(*slack.RTMEvent) bool
|
||||
Run()
|
||||
Exit()
|
||||
}
|
89
models/message.go
Normal file
89
models/message.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
)
|
||||
|
||||
const (
|
||||
MsgSrcApp = "app"
|
||||
MsgSrcSlack = "slack"
|
||||
MsgTpControl = "control"
|
||||
MsgTpError = "error"
|
||||
MsgTpMessage = "message"
|
||||
MsgTextQuit = "quit"
|
||||
)
|
||||
|
||||
type BotMessage struct {
|
||||
Source string
|
||||
Dest string
|
||||
Type string
|
||||
Target string
|
||||
Text string
|
||||
}
|
||||
|
||||
var QuitMessage = BotMessage{
|
||||
Source: MsgSrcApp,
|
||||
Dest: MsgSrcApp,
|
||||
Type: MsgTpControl,
|
||||
Target: MsgSrcApp,
|
||||
Text: MsgTextQuit,
|
||||
}
|
||||
|
||||
func NewBotMessage(src, dst, tp, tgt, msg string) BotMessage {
|
||||
return BotMessage{
|
||||
Source: src,
|
||||
Dest: dst,
|
||||
Type: tp,
|
||||
Target: tgt,
|
||||
Text: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func NewErrorBotMessage(src, tgt, msg string) BotMessage {
|
||||
return BotMessage{
|
||||
Source: src,
|
||||
Dest: MsgSrcApp,
|
||||
Type: MsgTpError,
|
||||
Target: tgt,
|
||||
Text: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func BotMessageFromSlack(msg slack.Message) BotMessage {
|
||||
tgt := msg.Channel
|
||||
if tgt == "" && msg.User != "" {
|
||||
tgt = msg.User
|
||||
}
|
||||
return BotMessage{
|
||||
Source: MsgSrcSlack,
|
||||
Dest: MsgSrcApp,
|
||||
Type: msg.Type,
|
||||
Target: tgt,
|
||||
Text: msg.Text,
|
||||
}
|
||||
}
|
||||
|
||||
func (b BotMessage) GetSource() string { return b.Source }
|
||||
func (b BotMessage) GetDestination() string { return b.Dest }
|
||||
func (b BotMessage) GetType() string { return b.Type }
|
||||
func (b BotMessage) GetTarget() string { return b.Target }
|
||||
func (b BotMessage) GetText() string { return b.Text }
|
||||
|
||||
func (b BotMessage) IsTo(dest string) bool { return b.Dest == dest }
|
||||
func (b BotMessage) IsError() bool { return b.Type == MsgTpError }
|
||||
func (b BotMessage) Is(msg BotMessage) bool {
|
||||
return b.Matches(msg.Source, msg.Dest, msg.Type, msg.Target, msg.Text)
|
||||
}
|
||||
func (b BotMessage) Matches(src, dest, tp, tgt, msg string) bool {
|
||||
return b.Source == src &&
|
||||
b.Dest == dest &&
|
||||
b.Type == tp &&
|
||||
b.Target == tgt &&
|
||||
b.Text == msg
|
||||
}
|
||||
|
||||
func (b BotMessage) String() string {
|
||||
return fmt.Sprintf("BotMessage: %s -> %s[%s]@%s :: %s", b.Source, b.Dest, b.Type, b.Target, b.Text)
|
||||
}
|
92
models/model.go
Normal file
92
models/model.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.bullercodeworks.com/brian/boltease"
|
||||
"github.com/slack-go/slack"
|
||||
)
|
||||
|
||||
type BotModel struct {
|
||||
debug bool
|
||||
db *boltease.DB
|
||||
|
||||
messages chan BotMessage
|
||||
|
||||
slackApiToken string
|
||||
slackApi *slack.Client
|
||||
slackRTM *slack.RTM
|
||||
slackRTMLatency time.Duration
|
||||
|
||||
incomingSlackMessages chan *slack.MessageEvent
|
||||
otherRTMEvents chan *slack.RTMEvent
|
||||
|
||||
messageWatchers []func(msg BotMessage) bool
|
||||
rtmWatchers []func(event *slack.RTMEvent) bool
|
||||
}
|
||||
|
||||
func NewBotModel(debug bool) (*BotModel, error) {
|
||||
var err error
|
||||
m := new(BotModel)
|
||||
m.debug = debug
|
||||
m.messages = make(chan BotMessage, 100)
|
||||
m.messageWatchers = make([]func(msg BotMessage) bool, 1)
|
||||
m.AddMessageWatcher(m.DebugMessageWatcher)
|
||||
m.AddRTMWatcher(m.DebugRTMWatcher)
|
||||
m.db, err = boltease.Create("helperbot.db", 0600, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = m.NewSlack(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go m.monitorSlackMessages()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *BotModel) DebugMessageWatcher(msg BotMessage) bool {
|
||||
if m.debug {
|
||||
fmt.Printf("Received Message: %s\n", msg)
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (m *BotModel) DebugRTMWatcher(event *slack.RTMEvent) bool {
|
||||
if m.debug {
|
||||
fmt.Printf("RTMEvent: %s\n", event)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *BotModel) SendMessage(msg BotMessage) {
|
||||
fmt.Println("Sending:", msg)
|
||||
m.messages <- msg
|
||||
}
|
||||
|
||||
func (m *BotModel) AddMessageWatcher(watcher func(msg BotMessage) bool) {
|
||||
m.messageWatchers = append(m.messageWatchers, watcher)
|
||||
}
|
||||
func (m *BotModel) AddRTMWatcher(watcher func(event *slack.RTMEvent) bool) {
|
||||
m.rtmWatchers = append(m.rtmWatchers, watcher)
|
||||
}
|
||||
|
||||
func (m *BotModel) ProcessMessageChannel() {
|
||||
msg := <-m.messages
|
||||
for _, v := range m.messageWatchers {
|
||||
// Pass the message to the watcher
|
||||
if v(msg) {
|
||||
// if a watcher returns true, the message was consumed
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
func (m *BotModel) ProcessRTMChannel() {
|
||||
msg := <-m.otherRTMEvents
|
||||
for _, v := range m.rtmWatchers {
|
||||
// Pass the event to the watcher
|
||||
if v(msg) {
|
||||
// if a watcher returns true, the message was consumed
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
62
models/model_bolt.go
Normal file
62
models/model_bolt.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (m *BotModel) GetBytes(path []string) ([]byte, error) {
|
||||
var err error
|
||||
var v []byte
|
||||
// Value is not cached, try to pull it from the DB
|
||||
if len(path) > 2 {
|
||||
path, key := path[:len(path)-1], path[len(path)-1]
|
||||
v, err = m.db.GetBytes(path, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (m *BotModel) SetBytes(path []string, val []byte) error {
|
||||
if len(path) > 1 {
|
||||
path, key := path[:len(path)-1], path[len(path)-1]
|
||||
err := m.db.SetBytes(path, key, val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.New("Invalid path")
|
||||
}
|
||||
|
||||
func (m *BotModel) GetString(path []string) (string, error) {
|
||||
bts, err := m.GetBytes(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(bts) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
return string(bts), nil
|
||||
}
|
||||
|
||||
func (m *BotModel) SetString(path []string, val string) error {
|
||||
return m.SetBytes(path, []byte(val))
|
||||
}
|
||||
|
||||
func (m *BotModel) GetInt(path []string) (int, error) {
|
||||
bts, err := m.GetBytes(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(bts) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return strconv.Atoi(string(bts))
|
||||
}
|
||||
|
||||
func (m *BotModel) SetInt(path []string, val int) error {
|
||||
return m.SetString(path, strconv.Itoa(val))
|
||||
}
|
156
models/model_slack.go
Normal file
156
models/model_slack.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
keyPluginDir = "plugin_dir"
|
||||
keySlackToken = "slack.token"
|
||||
keySlackAdminDmId = "slack.admin_dm_id"
|
||||
)
|
||||
|
||||
func (m *BotModel) GetPluginDir() string { return viper.GetString(keyPluginDir) }
|
||||
|
||||
/* Slack Config Functions */
|
||||
func (m *BotModel) getSlackToken() string { return viper.GetString(keySlackToken) }
|
||||
func (m *BotModel) setSlackToken(token string) error {
|
||||
viper.Set(keySlackToken, token)
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
func (m *BotModel) GetSlackAdminDMId() string { return viper.GetString(keySlackAdminDmId) }
|
||||
func (m *BotModel) setSlackAdminDMId(adminId string) error {
|
||||
viper.Set(keySlackAdminDmId, adminId)
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
func (m *BotModel) requestSlackToken() {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("Slack Token: ")
|
||||
token, _ := reader.ReadString('\n')
|
||||
m.slackApiToken = strings.TrimSpace(token)
|
||||
m.setSlackToken(m.slackApiToken)
|
||||
}
|
||||
|
||||
func (m *BotModel) requestAdminDMId() {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("Slack Admin DM ID: ")
|
||||
dmId, _ := reader.ReadString('\n')
|
||||
m.setSlackAdminDMId(strings.TrimSpace(dmId))
|
||||
}
|
||||
|
||||
/* End Slack Config Functions */
|
||||
|
||||
func (m *BotModel) NewSlack() error {
|
||||
var err error
|
||||
m.slackApiToken = m.getSlackToken()
|
||||
if m.slackApiToken == "" {
|
||||
m.requestSlackToken()
|
||||
}
|
||||
slackDmId := m.GetSlackAdminDMId()
|
||||
if slackDmId == "" {
|
||||
m.requestAdminDMId()
|
||||
}
|
||||
m.incomingSlackMessages = make(chan *slack.MessageEvent, 50)
|
||||
m.otherRTMEvents = make(chan *slack.RTMEvent, 50)
|
||||
m.slackApi = slack.New(m.slackApiToken)
|
||||
m.slackRTM = m.slackApi.NewRTM()
|
||||
m.slackRTMLatency = time.Duration(0)
|
||||
|
||||
go m.slackRTM.ManageConnection()
|
||||
go m.HandleRTMEvents()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *BotModel) GetChannelInfo(id string) (*slack.Channel, error) {
|
||||
convInfo := &slack.GetConversationInfoInput{
|
||||
ChannelID: id,
|
||||
IncludeLocale: false,
|
||||
IncludeNumMembers: false,
|
||||
}
|
||||
return m.slackApi.GetConversationInfo(convInfo)
|
||||
}
|
||||
|
||||
func (m *BotModel) HandleRTMEvents() {
|
||||
for msg := range m.slackRTM.IncomingEvents {
|
||||
fmt.Printf("RTM Message: %v\n", msg)
|
||||
|
||||
switch ev := msg.Data.(type) {
|
||||
case *slack.MessageEvent:
|
||||
fmt.Println("RTM Message: Processing Message Event")
|
||||
m.processMessageEvent(ev)
|
||||
|
||||
case *slack.LatencyReport:
|
||||
fmt.Println("RTM Message: Latency Report")
|
||||
m.otherRTMEvents <- &msg
|
||||
|
||||
case *slack.RTMError:
|
||||
fmt.Printf("RTM ERROR: (%d) %s\n", ev.Code, ev.Msg)
|
||||
m.otherRTMEvents <- &msg
|
||||
|
||||
default: // Ignore other events
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *BotModel) SendSlackAdminMessage(msg string) error {
|
||||
// Send message to slack admin
|
||||
dmId := m.GetSlackAdminDMId()
|
||||
_, _, err := m.slackApi.PostMessage(
|
||||
dmId,
|
||||
slack.MsgOptionText(msg, false),
|
||||
slack.MsgOptionAsUser(true),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *BotModel) SendSlackChannelMessage(msg string, cid string) error {
|
||||
if m.debug {
|
||||
return m.SendSlackAdminMessage(msg)
|
||||
}
|
||||
_, _, err := m.slackApi.PostMessage(
|
||||
cid,
|
||||
slack.MsgOptionText(msg, false),
|
||||
slack.MsgOptionAsUser(true),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *BotModel) processMessageEvent(ev *slack.MessageEvent) {
|
||||
m.getSlackUserName(ev.User)
|
||||
m.getSlackChannelName(ev.Channel)
|
||||
m.incomingSlackMessages <- ev
|
||||
}
|
||||
|
||||
func (m *BotModel) getSlackUserName(id string) (string, error) {
|
||||
user, err := m.slackApi.GetUserInfo(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return user.Profile.DisplayName, nil
|
||||
}
|
||||
func (m *BotModel) getSlackChannelName(id string) (string, error) {
|
||||
c, err := m.GetChannelInfo(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return c.Name, nil
|
||||
}
|
||||
func (m *BotModel) GetSlackLatency() time.Duration {
|
||||
return m.slackRTMLatency
|
||||
}
|
||||
|
||||
func (m *BotModel) monitorSlackMessages() {
|
||||
for msg := range m.incomingSlackMessages {
|
||||
m.SendMessage(BotMessageFromSlack(slack.Message(*msg)))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user