/
main.go
154 lines (136 loc) · 3.94 KB
/
main.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package main
import (
"bytes"
"database/sql"
"flag"
"fmt"
"os"
"path/filepath"
"golang.org/x/oauth2"
_ "github.com/go-sql-driver/mysql"
"github.com/keybase/go-keybase-chat-bot/kbchat"
"github.com/keybase/go-keybase-chat-bot/kbchat/types/chat1"
"github.com/keybase/managed-bots/base"
"github.com/keybase/managed-bots/meetbot/meetbot"
"golang.org/x/oauth2/google"
"golang.org/x/sync/errgroup"
"google.golang.org/api/calendar/v3"
)
type Options struct {
*base.Options
KBFSRoot string
}
func NewOptions() *Options {
return &Options{
Options: base.NewOptions(),
}
}
type BotServer struct {
*base.Server
opts Options
kbc *kbchat.API
}
func NewBotServer(opts Options) *BotServer {
return &BotServer{
Server: base.NewServer("meetbot", opts.Announcement, opts.AWSOpts, opts.MultiDSN, opts.ReadSelf, kbchat.RunOptions{
KeybaseLocation: opts.KeybaseLocation,
HomeDir: opts.Home,
}),
opts: opts,
}
}
func (s *BotServer) makeAdvertisement() kbchat.Advertisement {
return kbchat.Advertisement{
Alias: "Google Meet",
Advertisements: []chat1.AdvertiseCommandAPIParam{
{
Typ: "public",
Commands: []chat1.UserBotCommandInput{
{
Name: "meet",
Description: "New Google Meet",
},
base.GetFeedbackCommandAdvertisement(s.kbc.GetUsername()),
},
},
},
}
}
func (s *BotServer) getOAuthConfig() (config *oauth2.Config, err error) {
defer s.Trace(&err, "getOAuthConfig")()
if len(s.opts.KBFSRoot) == 0 {
return nil, fmt.Errorf("BOT_KBFS_ROOT must be specified\n")
}
configPath := filepath.Join(s.opts.KBFSRoot, "credentials.json")
cmd := s.opts.Command("fs", "read", configPath)
var out bytes.Buffer
cmd.Stdout = &out
fmt.Printf("Running `keybase fs read` on %q and waiting for it to finish...\n", configPath)
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("could not read credentials.json: %v", err)
}
// If modifying these scopes, drop the saved tokens in the db
config, err = google.ConfigFromJSON(out.Bytes(), calendar.CalendarEventsScope)
if err != nil {
return nil, fmt.Errorf("unable to parse client secret file to config: %v", err)
}
return config, nil
}
func (s *BotServer) Go() (err error) {
if s.kbc, err = s.Start(s.opts.ErrReportConv); err != nil {
return fmt.Errorf("failed to start keybase %v", err)
}
config, err := s.getOAuthConfig()
if err != nil {
return fmt.Errorf("failed to get config %v", err)
}
sdb, err := sql.Open("mysql", s.opts.DSN)
if err != nil {
s.Errorf("failed to connect to MySQL: %s", err)
return err
}
defer sdb.Close()
db := base.NewOAuthDB(sdb)
debugConfig := base.NewChatDebugOutputConfig(s.kbc, s.opts.ErrReportConv)
stats, err := base.NewStatsRegistry(debugConfig, s.opts.StathatEZKey)
if err != nil {
s.Debug("unable to create stats: %v", err)
return err
}
stats = stats.SetPrefix(s.Name())
handler := meetbot.NewHandler(stats, s.kbc, debugConfig, db, config)
httpSrv := meetbot.NewHTTPSrv(stats, s.kbc, debugConfig, db, handler, config)
eg := &errgroup.Group{}
s.GoWithRecover(eg, func() error { return s.Listen(handler) })
s.GoWithRecover(eg, httpSrv.Listen)
s.GoWithRecover(eg, func() error { return s.HandleSignals(httpSrv, stats) })
s.GoWithRecover(eg, func() error { return s.AnnounceAndAdvertise(s.makeAdvertisement(), "I live.") })
if err := eg.Wait(); err != nil {
s.Debug("wait error: %s", err)
return err
}
return nil
}
func main() {
rc := mainInner()
os.Exit(rc)
}
func mainInner() int {
opts := NewOptions()
fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
fs.StringVar(&opts.KBFSRoot, "kbfs-root", os.Getenv("BOT_KBFS_ROOT"), "root path to bot's KBFS backed config")
if err := opts.Parse(fs, os.Args); err != nil {
fmt.Printf("Unable to parse options: %v\n", err)
return 3
}
if len(opts.DSN) == 0 {
fmt.Printf("must specify a database DSN\n")
return 3
}
bs := NewBotServer(*opts)
if err := bs.Go(); err != nil {
fmt.Printf("error running chat loop: %s\n", err)
return 3
}
return 0
}