This repository has been archived by the owner on Nov 9, 2017. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
151 lines (131 loc) · 4.38 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
package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"github.com/jorinvo/studybot/admin"
"github.com/jorinvo/studybot/brain"
"github.com/jorinvo/studybot/messenger"
)
const cliUsage = `Studybot - Facebook Messenger bot
Usage: %s [flags]
Studybot uses BoltDB as a database.
Data is stored in a single file. No external system is needed.
However, only one application can access the database at a time.
Studybot starts a server to serve a webhook handler that can be registered as a Messenger bot.
The server is HTTP only and a proxy server should be used to make the bot available on
a public domain, preferably HTTPS only.
An admin server runs on a separate port.
It should be proxied and secured via HTTPS + basic auth.
The admin server provides an endpoint to fetch backups of the database.
Further, it provides an endpoint that can be registered as Slack Outgoing Webhook.
When users send feedback to the bot, the messages are forwarded to Slack
and admin replies in Slack are send back to the users.
Flags:
`
func main() {
errorLogger := log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lshortfile|log.LUTC)
infoLogger := log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile|log.LUTC)
db := flag.String("db", "", "Required. Path to BoltDB file. Will be created if non-existent.")
port := flag.Int("port", 8080, "Port Facebook webhook listens on.")
verifyToken := flag.String("verify", "", "Required. Messenger bot verify token.")
token := flag.String("token", "", "Required. Messenger bot token.")
slackHook := flag.String("slackhook", "", "Required. URL of Slack Incoming Webhook. Used to send user messages to admin.")
slackToken := flag.String("slacktoken", "", "Token for Slack Outgoing Webhook. Used to send admin answers to user messages.")
adminPort := flag.Int("admin", 8081, "Port admin interface listens on.")
// Parse and validate flags
flag.Usage = func() {
fmt.Fprintf(os.Stderr, cliUsage, os.Args[0])
flag.PrintDefaults()
}
flag.Parse()
if *db == "" {
errorLogger.Println("Flag -db is required.")
os.Exit(1)
}
if *token == "" {
errorLogger.Println("Flag -token is required.")
os.Exit(1)
}
if *verifyToken == "" {
errorLogger.Println("Flag -verify is required.")
os.Exit(1)
}
if *slackHook == "" {
errorLogger.Println("Flag -slackhook is required.")
os.Exit(1)
}
// Setup database
store, err := brain.New(*db)
if err != nil {
errorLogger.Fatalln("failed to create store:", err)
}
defer func() {
err := store.Close()
if err != nil {
errorLogger.Println(err)
}
}()
infoLogger.Printf("Database initialized: %s", *db)
// Listen to system events for graceful shutdown
shutdownSignals := make(chan os.Signal, 1)
signal.Notify(shutdownSignals, os.Interrupt)
// Start Facebook webhook server
feedback := make(chan messenger.Feedback)
bot, err := messenger.New(
store,
*token,
messenger.Verify(*verifyToken),
messenger.LogInfo(infoLogger),
messenger.LogErr(errorLogger),
messenger.GetFeedback(feedback),
messenger.Setup,
messenger.Notify,
)
if err != nil {
log.Fatalln("failed to start messenger:", err)
}
mAddr := "localhost:" + strconv.Itoa(*port)
messengerServer := &http.Server{Addr: mAddr, Handler: bot}
go func() {
infoLogger.Printf("Messenger webhook server running at %s", mAddr)
if err := messengerServer.ListenAndServe(); err != nil {
errorLogger.Fatalln("failed to start server:", err)
}
}()
// Setup admin
adminHandler := admin.New(
store,
*slackHook,
admin.SlackReply(*slackToken, bot.SendMessage),
admin.LogErr(errorLogger),
)
aAddr := "localhost:" + strconv.Itoa(*adminPort)
adminServer := &http.Server{Addr: aAddr, Handler: adminHandler}
go func() {
infoLogger.Printf("Admin server running at %s", aAddr)
if err := adminServer.ListenAndServe(); err != nil {
errorLogger.Fatalln("failed to start server:", err)
}
}()
go func() {
for f := range feedback {
adminHandler.HandleMessage(f.ChatID, f.Username, f.Message)
}
}()
// Wait for shutdown
<-shutdownSignals
infoLogger.Println("Waiting for connections before shutting down server.")
if err = messengerServer.Shutdown(context.Background()); err != nil {
errorLogger.Fatalln("failed to shutdown gracefully:", err)
}
if err = adminServer.Shutdown(context.Background()); err != nil {
errorLogger.Fatalln("failed to shutdown gracefully:", err)
}
infoLogger.Println("Server gracefully stopped.")
}