-
Notifications
You must be signed in to change notification settings - Fork 27
/
messenger.go
163 lines (145 loc) · 4.83 KB
/
messenger.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
155
156
157
158
159
160
161
162
163
package messenger
import (
"crypto/hmac"
"crypto/sha1"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
)
// GraphAPI specifies host used for API requests
var GraphAPI = "https://graph.facebook.com"
type (
// MessageReceivedHandler is called when a new message is received
MessageReceivedHandler func(Event, MessageOpts, ReceivedMessage)
// MessageDeliveredHandler is called when a message sent has been successfully delivered
MessageDeliveredHandler func(Event, MessageOpts, Delivery)
// PostbackHandler is called when the postback button has been pressed by recipient
PostbackHandler func(Event, MessageOpts, Postback)
// AuthenticationHandler is called when a new user joins/authenticates
AuthenticationHandler func(Event, MessageOpts, *Optin)
// MessageReadHandler is called when a message has been read by recipient
MessageReadHandler func(Event, MessageOpts, Read)
// MessageEchoHandler is called when a message is sent by your page
MessageEchoHandler func(Event, MessageOpts, MessageEcho)
)
// DebugType describes available debug type options as documented on https://developers.facebook.com/docs/graph-api/using-graph-api#debugging
type DebugType string
const (
// DebugAll returns all available debug messages
DebugAll DebugType = "all"
// DebugInfo returns debug messages with type info or warning
DebugInfo DebugType = "info"
// DebugWarning returns debug messages with type warning
DebugWarning DebugType = "warning"
)
// Messenger is the main service which handles all callbacks from facebook
// Events are delivered to handlers if they are specified
type Messenger struct {
VerifyToken string
AppSecret string
AccessToken string
Debug DebugType
MessageReceived MessageReceivedHandler
MessageDelivered MessageDeliveredHandler
Postback PostbackHandler
Authentication AuthenticationHandler
MessageRead MessageReadHandler
MessageEcho MessageEchoHandler
Client *http.Client
}
// Handler is the main HTTP handler for the Messenger service.
// It MUST be attached to some web server in order to receive messages
func (m *Messenger) Handler(rw http.ResponseWriter, req *http.Request) {
if req.Method == "GET" {
query := req.URL.Query()
if query.Get("hub.verify_token") != m.VerifyToken {
rw.WriteHeader(http.StatusUnauthorized)
return
}
rw.WriteHeader(http.StatusOK)
rw.Write([]byte(query.Get("hub.challenge")))
} else if req.Method == "POST" {
m.handlePOST(rw, req)
} else {
rw.WriteHeader(http.StatusMethodNotAllowed)
}
}
func (m *Messenger) handlePOST(rw http.ResponseWriter, req *http.Request) {
read, err := ioutil.ReadAll(req.Body)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
return
}
defer req.Body.Close()
//Message integrity check
if m.AppSecret != "" {
if len(req.Header.Get("x-hub-signature")) < 6 || !checkIntegrity(m.AppSecret, read, req.Header.Get("x-hub-signature")[5:]) {
rw.WriteHeader(http.StatusBadRequest)
return
}
}
event := &upstreamEvent{}
err = json.Unmarshal(read, event)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
return
}
for _, entry := range event.Entries {
entry.Event.Request = req
for _, message := range entry.Messaging {
if message.Delivery != nil {
if m.MessageDelivered != nil {
go m.MessageDelivered(entry.Event, message.MessageOpts, *message.Delivery)
}
} else if message.Message != nil && message.Message.IsEcho {
if m.MessageEcho != nil {
go m.MessageEcho(entry.Event, message.MessageOpts, *message.Message)
}
} else if message.Message != nil {
if m.MessageReceived != nil {
go m.MessageReceived(entry.Event, message.MessageOpts, message.Message.ReceivedMessage)
}
} else if message.Postback != nil {
if m.Postback != nil {
go m.Postback(entry.Event, message.MessageOpts, *message.Postback)
}
} else if message.Read != nil {
if m.MessageRead != nil {
go m.MessageRead(entry.Event, message.MessageOpts, *message.Read)
}
} else if m.Authentication != nil {
go m.Authentication(entry.Event, message.MessageOpts, message.Optin)
}
}
}
rw.WriteHeader(http.StatusOK)
rw.Write([]byte(`{"status":"ok"}`))
}
func checkIntegrity(appSecret string, bytes []byte, expectedSignature string) bool {
mac := hmac.New(sha1.New, []byte(appSecret))
mac.Write(bytes)
if fmt.Sprintf("%x", mac.Sum(nil)) != expectedSignature {
return false
}
return true
}
func (m *Messenger) doRequest(method string, url string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
query := req.URL.Query()
query.Set("access_token", m.AccessToken)
if m.Debug != "" {
query.Set("debug", string(m.Debug))
}
req.URL.RawQuery = query.Encode()
if m.Client != nil {
return m.Client.Do(req)
} else {
return http.DefaultClient.Do(req)
}
}