Skip to content

Commit d7be19a

Browse files
authored
add support to run darwinbot on keybase chat (#67)
1 parent cd40397 commit d7be19a

File tree

18 files changed

+924
-210
lines changed

18 files changed

+924
-210
lines changed

bot.go

Lines changed: 68 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -11,103 +11,89 @@ import (
1111
"sort"
1212
"strings"
1313
"text/tabwriter"
14-
15-
"github.com/nlopes/slack"
1614
)
1715

18-
// Bot describes a generic bot
19-
type Bot interface {
20-
Name() string
21-
Config() Config
22-
AddCommand(trigger string, command Command)
16+
type BotCommandRunner interface {
17+
RunCommand(args []string, channel string) error
18+
}
19+
20+
type BotBackend interface {
2321
SendMessage(text string, channel string)
24-
HelpMessage() string
25-
SetHelp(help string)
26-
Label() string
27-
SetDefault(command Command)
28-
Listen()
22+
Listen(BotCommandRunner)
2923
}
3024

31-
// SlackBot is a Slack bot
32-
type SlackBot struct {
33-
api *slack.Client
34-
rtm *slack.RTM
35-
commands map[string]Command
36-
defaultCommand Command
37-
channelIDs map[string]string
25+
// Bot describes a generic bot
26+
type Bot struct {
27+
backend BotBackend
3828
help string
3929
name string
4030
label string
4131
config Config
32+
commands map[string]Command
33+
defaultCommand Command
4234
}
4335

44-
// NewBot constructs a bot from a Slack token
45-
func NewBot(token string, name string, label string, config Config) (Bot, error) {
46-
api := slack.New(token)
47-
//api.SetDebug(true)
48-
49-
channelIDs, err := LoadChannelIDs(*api)
50-
if err != nil {
51-
return nil, err
52-
}
53-
54-
bot := newBot(config)
55-
bot.api = api
56-
bot.rtm = api.NewRTM()
57-
bot.channelIDs = channelIDs
58-
bot.name = name
59-
bot.label = label
60-
61-
return bot, nil
62-
}
63-
64-
func newBot(config Config) *SlackBot {
65-
bot := SlackBot{
36+
func NewBot(config Config, name, label string, backend BotBackend) *Bot {
37+
return &Bot{
38+
backend: backend,
6639
config: config,
6740
commands: make(map[string]Command),
41+
name: name,
42+
label: label,
6843
}
69-
return &bot
7044
}
7145

72-
// NewTestBot returns a bot for testing
73-
func NewTestBot() (Bot, error) {
74-
return newBot(NewConfig(true, false)), nil
46+
func (b *Bot) Name() string {
47+
return b.name
7548
}
7649

77-
// AddCommand adds a command to the Bot
78-
func (b *SlackBot) AddCommand(trigger string, command Command) {
79-
b.commands[trigger] = command
50+
func (b *Bot) Config() Config {
51+
return b.config
8052
}
8153

82-
// Name returns bot name
83-
func (b *SlackBot) Name() string {
84-
return b.name
54+
func (b *Bot) AddCommand(trigger string, command Command) {
55+
b.commands[trigger] = command
8556
}
8657

87-
// Label returns bot label
88-
func (b *SlackBot) Label() string {
89-
return b.label
58+
func (b *Bot) triggers() []string {
59+
triggers := make([]string, 0, len(b.commands))
60+
for trigger := range b.commands {
61+
triggers = append(triggers, trigger)
62+
}
63+
sort.Strings(triggers)
64+
return triggers
9065
}
9166

92-
// Config returns bot config
93-
func (b *SlackBot) Config() Config {
94-
return b.config
67+
// HelpMessage is the default help message for the bot
68+
func (b *Bot) HelpMessage() string {
69+
w := new(tabwriter.Writer)
70+
buf := new(bytes.Buffer)
71+
w.Init(buf, 8, 8, 8, ' ', 0)
72+
fmt.Fprintln(w, "Command\tDescription")
73+
for _, trigger := range b.triggers() {
74+
command := b.commands[trigger]
75+
fmt.Fprintln(w, fmt.Sprintf("%s\t%s", trigger, command.Description()))
76+
}
77+
_ = w.Flush()
78+
return BlockQuote(buf.String())
9579
}
9680

97-
// SetHelp sets the help info
98-
func (b *SlackBot) SetHelp(help string) {
81+
func (b *Bot) SetHelp(help string) {
9982
b.help = help
10083
}
10184

102-
// SetDefault is the default command, if no command added for trigger
103-
func (b *SlackBot) SetDefault(command Command) {
85+
func (b *Bot) Label() string {
86+
return b.label
87+
}
88+
89+
func (b *Bot) SetDefault(command Command) {
10490
b.defaultCommand = command
10591
}
10692

10793
// RunCommand runs a command
108-
func (b *SlackBot) RunCommand(args []string, channel string) error {
94+
func (b *Bot) RunCommand(args []string, channel string) error {
10995
if len(args) == 0 || args[0] == "help" {
110-
b.SendHelpMessage(channel)
96+
b.sendHelpMessage(channel)
11197
return nil
11298
}
11399

@@ -121,128 +107,52 @@ func (b *SlackBot) RunCommand(args []string, channel string) error {
121107
}
122108

123109
if args[0] != "resume" && args[0] != "config" && b.Config().Paused() {
124-
b.SendMessage("I can't do that, I'm paused.", channel)
110+
b.backend.SendMessage("I can't do that, I'm paused.", channel)
125111
return nil
126112
}
127113

128114
go b.run(args, command, channel)
129115
return nil
130116
}
131117

132-
func (b *SlackBot) run(args []string, command Command, channel string) {
118+
func (b *Bot) run(args []string, command Command, channel string) {
133119
out, err := command.Run(channel, args)
134120
if err != nil {
135121
log.Printf("Error %s running: %#v; %s\n", err, command, out)
136-
b.SendMessage(fmt.Sprintf("Oops, there was an error in %q:\n%s", strings.Join(args, " "), SlackBlockQuote(out)), channel)
122+
b.backend.SendMessage(fmt.Sprintf("Oops, there was an error in %q:\n%s", strings.Join(args, " "),
123+
BlockQuote(out)), channel)
137124
return
138125
}
139126
log.Printf("Output: %s\n", out)
140127
if command.ShowResult() {
141-
b.SendMessage(out, channel)
128+
b.backend.SendMessage(out, channel)
142129
}
143130
}
144131

145-
// SendMessage sends a message to a channel
146-
func (b *SlackBot) SendMessage(text string, channel string) {
147-
cid := b.channelIDs[channel]
148-
if cid == "" {
149-
cid = channel
150-
}
151-
152-
if channel == "" {
153-
log.Printf("No channel to send message: %s", text)
154-
return
155-
}
156-
157-
if b.rtm != nil {
158-
b.rtm.SendMessage(b.rtm.NewOutgoingMessage(text, cid))
159-
} else {
160-
log.Printf("Unable to send message: %s", text)
161-
}
162-
}
163-
164-
// Triggers returns list of supported triggers
165-
func (b *SlackBot) Triggers() []string {
166-
triggers := make([]string, 0, len(b.commands))
167-
for trigger := range b.commands {
168-
triggers = append(triggers, trigger)
169-
}
170-
sort.Strings(triggers)
171-
return triggers
172-
}
173-
174-
// HelpMessage is the default help message for the bot
175-
func (b *SlackBot) HelpMessage() string {
176-
w := new(tabwriter.Writer)
177-
buf := new(bytes.Buffer)
178-
w.Init(buf, 8, 8, 8, ' ', 0)
179-
fmt.Fprintln(w, "Command\tDescription")
180-
for _, trigger := range b.Triggers() {
181-
command := b.commands[trigger]
182-
fmt.Fprintln(w, fmt.Sprintf("%s\t%s", trigger, command.Description()))
183-
}
184-
_ = w.Flush()
185-
186-
return SlackBlockQuote(buf.String())
187-
}
188-
189-
// SendHelpMessage displays help message to the channel
190-
func (b *SlackBot) SendHelpMessage(channel string) {
132+
func (b *Bot) sendHelpMessage(channel string) {
191133
help := b.help
192134
if help == "" {
193135
help = b.HelpMessage()
194136
}
195-
b.SendMessage(help, channel)
137+
b.backend.SendMessage(help, channel)
196138
}
197139

198-
// Listen starts listening on the connection
199-
func (b *SlackBot) Listen() {
200-
go b.rtm.ManageConnection()
201-
202-
auth, err := b.api.AuthTest()
203-
if err != nil {
204-
panic(err)
205-
}
206-
// The Slack bot "tuxbot" should expect commands to start with "!tuxbot".
207-
log.Printf("Connected to Slack as %q", auth.User)
208-
commandPrefix := "!" + auth.User
209-
210-
Loop:
211-
for {
212-
msg := <-b.rtm.IncomingEvents
213-
switch ev := msg.Data.(type) {
214-
case *slack.HelloEvent:
215-
216-
case *slack.ConnectedEvent:
217-
218-
case *slack.MessageEvent:
219-
args := parseInput(ev.Text)
220-
if len(args) > 0 && args[0] == commandPrefix {
221-
cmd := args[1:]
222-
b.RunCommand(cmd, ev.Channel)
223-
}
224-
225-
case *slack.PresenceChangeEvent:
226-
//log.Printf("Presence Change: %v\n", ev)
227-
228-
case *slack.LatencyReport:
229-
//log.Printf("Current latency: %v\n", ev.Value)
230-
231-
case *slack.RTMError:
232-
log.Printf("Error: %s\n", ev.Error())
140+
func (b *Bot) SendMessage(text string, channel string) {
141+
b.backend.SendMessage(text, channel)
142+
}
233143

234-
case *slack.InvalidAuthEvent:
235-
log.Printf("Invalid credentials\n")
236-
break Loop
144+
func (b *Bot) Listen() {
145+
b.backend.Listen(b)
146+
}
237147

238-
default:
239-
// log.Printf("Unexpected: %v\n", msg.Data)
240-
}
241-
}
148+
// NewTestBot returns a bot for testing
149+
func NewTestBot() (*Bot, error) {
150+
backend := &SlackBotBackend{}
151+
return NewBot(NewConfig(true, false), "testbot", "", backend), nil
242152
}
243153

244-
// SlackBlockQuote returns the string block-quoted
245-
func SlackBlockQuote(s string) string {
154+
// BlockQuote returns the string block-quoted
155+
func BlockQuote(s string) string {
246156
if !strings.HasSuffix(s, "\n") {
247157
s += "\n"
248158
}

cli/cli.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func Parse(app *kingpin.Application, args []string, stringBuffer *bytes.Buffer)
4242
}
4343

4444
if stringBuffer.Len() > 0 {
45-
return "", slackbot.SlackBlockQuote(stringBuffer.String()), nil
45+
return "", slackbot.BlockQuote(stringBuffer.String()), nil
4646
}
4747

4848
return cmd, "", err

examplebot/extension.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414

1515
type extension struct{}
1616

17-
func (e *extension) Run(bot slackbot.Bot, channel string, args []string) (string, error) {
17+
func (e *extension) Run(bot *slackbot.Bot, channel string, args []string) (string, error) {
1818
app := kingpin.New("examplebot", "Kingpin extension")
1919
app.Terminate(nil)
2020
stringBuffer := new(bytes.Buffer)
@@ -39,7 +39,7 @@ func (e *extension) Run(bot slackbot.Bot, channel string, args []string) (string
3939
return cmd, nil
4040
}
4141

42-
func (e *extension) Help(bot slackbot.Bot) string {
42+
func (e *extension) Help(bot *slackbot.Bot) string {
4343
out, err := e.Run(bot, "", nil)
4444
if err != nil {
4545
return fmt.Sprintf("Error getting help: %s", err)

examplebot/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ type runner struct{}
1313

1414
func main() {
1515
config := slackbot.NewConfig(false, false)
16-
bot, err := slackbot.NewBot(slackbot.GetTokenFromEnv(), "examplebot", "", config)
16+
backend, err := slackbot.NewSlackBotBackend(slackbot.GetTokenFromEnv())
1717
if err != nil {
1818
log.Fatal(err)
1919
}
20+
bot := slackbot.NewBot(config, "examplebot", "", backend)
2021

2122
// Command that runs and shows date
2223
bot.AddCommand("date", slackbot.NewExecCommand("/bin/date", nil, true, "Show the current date", config))

hybrid.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package slackbot
2+
3+
import (
4+
"sync"
5+
)
6+
7+
type hybridRunner struct {
8+
runner BotCommandRunner
9+
channel string
10+
}
11+
12+
func newHybridRunner(runner BotCommandRunner, channel string) *hybridRunner {
13+
return &hybridRunner{
14+
runner: runner,
15+
channel: channel,
16+
}
17+
}
18+
19+
func (r *hybridRunner) RunCommand(args []string, channel string) error {
20+
return r.runner.RunCommand(args, r.channel)
21+
22+
}
23+
24+
type HybridBackendMember struct {
25+
Backend BotBackend
26+
Channel string
27+
}
28+
29+
type HybridBackend struct {
30+
backends []HybridBackendMember
31+
}
32+
33+
func NewHybridBackend(backends ...HybridBackendMember) *HybridBackend {
34+
return &HybridBackend{
35+
backends: backends,
36+
}
37+
}
38+
39+
func (b *HybridBackend) SendMessage(text string, channel string) {
40+
for _, backend := range b.backends {
41+
backend.Backend.SendMessage(text, backend.Channel)
42+
}
43+
}
44+
45+
func (b *HybridBackend) Listen(runner BotCommandRunner) {
46+
var wg sync.WaitGroup
47+
for _, backend := range b.backends {
48+
wg.Add(1)
49+
go func() {
50+
backend.Backend.Listen(newHybridRunner(runner, backend.Channel))
51+
wg.Done()
52+
}()
53+
}
54+
wg.Wait()
55+
}

0 commit comments

Comments
 (0)