/
bot.go
122 lines (98 loc) · 2.69 KB
/
bot.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package main
import (
"fmt"
"log"
"regexp"
"strings"
"time"
"github.com/nlopes/slack"
"github.com/pteichman/fate"
)
type Bot struct {
RTM *slack.RTM
Model *fate.Model
users []slack.User
}
func newBot(rtm *slack.RTM, model *fate.Model) (*Bot, error) {
// TODO: don't fetch users at start, so new users are recognized.
users, err := rtm.GetUsers()
if err != nil {
return nil, err
}
bot := &Bot{
RTM: rtm,
Model: model,
users: users,
}
return bot, nil
}
// toRe matches messages to users.
var toRe = regexp.MustCompile("@[^ ]+")
func (b *Bot) handle(e slack.RTMEvent) {
info := b.RTM.GetInfo()
switch ev := e.Data.(type) {
case *slack.MessageEvent:
if !(ev.Type == "message" && ev.SubType == "") {
return
}
if ev.User == info.User.ID {
// Don't ever learn anything from ourselves.
return
}
text := strings.TrimSpace(cleanText(b.users, ev.Text))
// Looking here for @mentions of any user. If the mention is at the beginning
// of the message, strip it before continuing.
m := toRe.FindStringSubmatch(text)
if len(m) > 0 && strings.HasPrefix(text, m[0]) {
text = strings.TrimSpace(strings.TrimPrefix(text, m[0]))
}
log.Printf("Learning: '%s'", text)
b.Model.Learn(text)
from := getUserByID(b.users, ev.User)
if from != nil && from.IsBot {
return
}
for _, mention := range m {
if mention == "@"+info.User.Name {
b.RTM.SendMessage(b.RTM.NewTypingMessage(ev.Channel))
time.Sleep(time.Second / 2)
reply := b.Model.Reply(text)
log.Printf("Replying: '%s'", reply)
msg := fmt.Sprintf("<@%s> %s", ev.User, reply)
b.RTM.SendMessage(b.RTM.NewOutgoingMessage(msg, ev.Channel))
break
}
}
}
}
func getUserByID(users []slack.User, id string) *slack.User {
for _, user := range users {
if user.ID == id {
return &user
}
}
return nil
}
// Regexps for recovering what a user might have typed when Slack makes more
// full-featured text.
var (
// chanRe is for mentioning channels like #foo.
chanRe = regexp.MustCompile(`\x{003c}#.*?\|(.*?)\x{003e}`)
// userRe is for mentioning users like @foo.
userRe = regexp.MustCompile(`\x{003c}@(.*?)\x{003e}`)
// linkRe is for mentioning http or https links.
linkRe = regexp.MustCompile(`\x{003c}(https?://.*?)(\|.*)?\x{003e}`)
)
// cleanText attempts to recover what a user actually typed from text.
func cleanText(users []slack.User, text string) string {
text = chanRe.ReplaceAllString(text, "$1")
text = userRe.ReplaceAllStringFunc(text, func(match string) string {
m := userRe.FindStringSubmatch(match)
if user := getUserByID(users, m[1]); user != nil {
return "@" + user.Name
}
return "@unknown"
})
text = linkRe.ReplaceAllString(text, "$1")
return text
}