2016-04-13 05:03:26 +03:00
|
|
|
package messenger
|
|
|
|
|
|
|
|
import (
|
2016-04-13 09:14:23 +03:00
|
|
|
"encoding/json"
|
2016-04-13 05:03:26 +03:00
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2016-04-13 09:14:23 +03:00
|
|
|
"time"
|
2016-04-13 05:03:26 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2016-04-14 02:45:06 +03:00
|
|
|
// WebhookURL is where the Messenger client should listen for webhook events.
|
2016-04-13 05:03:26 +03:00
|
|
|
WebhookURL = "/webhook"
|
2016-04-15 03:16:52 +03:00
|
|
|
|
|
|
|
// ProfileURL is the API endpoint used for retrieving profiles.
|
|
|
|
// Used in the form: https://graph.facebook.com/v2.6/<USER_ID>?fields=first_name,last_name,profile_pic&access_token=<PAGE_ACCESS_TOKEN>
|
|
|
|
ProfileURL = "https://graph.facebook.com/v2.6/"
|
2016-04-13 05:03:26 +03:00
|
|
|
)
|
|
|
|
|
2016-04-14 03:25:48 +03:00
|
|
|
// Options are the settings used when creating a Messenger client.
|
|
|
|
type Options struct {
|
2016-04-14 02:45:06 +03:00
|
|
|
// Verify sets whether or not to be in the "verify" mode. Used for
|
|
|
|
// verifying webhooks on the Facebook Developer Portal.
|
|
|
|
Verify bool
|
|
|
|
// VerifyToken is the token to be used when verifying the webhook. Is set
|
|
|
|
// when the webhook is created.
|
2016-04-13 05:03:26 +03:00
|
|
|
VerifyToken string
|
2016-04-14 02:45:06 +03:00
|
|
|
// Token is the access token of the Facebook page to send messages from.
|
|
|
|
Token string
|
2016-04-13 05:03:26 +03:00
|
|
|
}
|
|
|
|
|
2016-04-14 02:45:06 +03:00
|
|
|
// MessageHandler is a handler used for responding to a message containing text.
|
2016-04-13 10:01:42 +03:00
|
|
|
type MessageHandler func(Message, *Response)
|
2016-04-14 02:45:06 +03:00
|
|
|
|
|
|
|
// DeliveryHandler is a handler used for responding to a read receipt.
|
2016-04-13 12:26:31 +03:00
|
|
|
type DeliveryHandler func(Delivery, *Response)
|
2016-04-13 09:14:23 +03:00
|
|
|
|
2016-04-14 02:45:06 +03:00
|
|
|
// Messenger is the client which manages communication with the Messenger Platform API.
|
2016-04-13 05:03:26 +03:00
|
|
|
type Messenger struct {
|
2016-04-13 12:26:31 +03:00
|
|
|
mux *http.ServeMux
|
|
|
|
messageHandlers []MessageHandler
|
|
|
|
deliveryHandlers []DeliveryHandler
|
|
|
|
token string
|
2016-04-18 13:03:57 +03:00
|
|
|
verifyHandler func(http.ResponseWriter, *http.Request)
|
2016-04-13 05:03:26 +03:00
|
|
|
}
|
|
|
|
|
2016-04-14 03:25:48 +03:00
|
|
|
// New creates a new Messenger. You pass in Options in order to affect settings.
|
|
|
|
func New(mo Options) *Messenger {
|
2016-04-13 05:03:26 +03:00
|
|
|
m := &Messenger{
|
2016-04-13 12:26:31 +03:00
|
|
|
mux: http.NewServeMux(),
|
|
|
|
token: mo.Token,
|
2016-04-13 05:03:26 +03:00
|
|
|
}
|
|
|
|
|
2016-04-18 13:03:57 +03:00
|
|
|
m.verifyHandler = newVerifyHandler(mo.VerifyToken)
|
|
|
|
m.mux.HandleFunc(WebhookURL, m.handle)
|
2016-04-13 05:03:26 +03:00
|
|
|
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
2016-04-14 02:45:06 +03:00
|
|
|
// HandleMessage adds a new MessageHandler to the Messenger which will be triggered
|
2016-04-14 03:27:05 +03:00
|
|
|
// when a message is received by the client.
|
2016-04-13 12:26:31 +03:00
|
|
|
func (m *Messenger) HandleMessage(f MessageHandler) {
|
|
|
|
m.messageHandlers = append(m.messageHandlers, f)
|
|
|
|
}
|
|
|
|
|
2016-04-14 02:45:06 +03:00
|
|
|
// HandleDelivery adds a new DeliveryHandler to the Messenger which will be triggered
|
|
|
|
// when a previously sent message is read by the recipient.
|
2016-04-13 12:26:31 +03:00
|
|
|
func (m *Messenger) HandleDelivery(f DeliveryHandler) {
|
|
|
|
m.deliveryHandlers = append(m.deliveryHandlers, f)
|
2016-04-13 09:14:23 +03:00
|
|
|
}
|
|
|
|
|
2016-04-14 02:45:06 +03:00
|
|
|
// Handler returns the Messenger in HTTP client form.
|
2016-04-13 05:03:26 +03:00
|
|
|
func (m *Messenger) Handler() http.Handler {
|
|
|
|
return m.mux
|
|
|
|
}
|
|
|
|
|
2016-04-15 03:16:52 +03:00
|
|
|
// ProfileByID retrieves the Facebook user associated with that ID
|
|
|
|
func (m *Messenger) ProfileByID(id int64) (Profile, error) {
|
|
|
|
p := Profile{}
|
|
|
|
url := fmt.Sprintf("%v%v", ProfileURL, id)
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
|
|
if err != nil {
|
|
|
|
return p, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.URL.RawQuery = "fields=first_name,last_name,profile_pic&access_token=" + m.token
|
|
|
|
|
|
|
|
client := &http.Client{}
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
err = json.NewDecoder(resp.Body).Decode(&p)
|
|
|
|
|
|
|
|
return p, err
|
|
|
|
}
|
|
|
|
|
2016-04-14 02:45:06 +03:00
|
|
|
// handle is the internal HTTP handler for the webhooks.
|
2016-04-13 09:14:23 +03:00
|
|
|
func (m *Messenger) handle(w http.ResponseWriter, r *http.Request) {
|
2016-04-18 13:03:57 +03:00
|
|
|
if r.Method == "GET" {
|
|
|
|
m.verifyHandler(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-04-13 09:14:23 +03:00
|
|
|
var rec Receive
|
|
|
|
|
|
|
|
err := json.NewDecoder(r.Body).Decode(&rec)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
|
|
|
|
fmt.Fprintln(w, `{status: 'not ok'}`)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if rec.Object != "page" {
|
|
|
|
fmt.Println("Object is not page, undefined behaviour. Got", rec.Object)
|
|
|
|
}
|
|
|
|
|
|
|
|
m.dispatch(rec)
|
|
|
|
|
|
|
|
fmt.Fprintln(w, `{status: 'ok'}`)
|
|
|
|
}
|
|
|
|
|
2016-04-14 02:45:06 +03:00
|
|
|
// dispatch triggers all of the relevant handlers when a webhook event is received.
|
2016-04-13 09:14:23 +03:00
|
|
|
func (m *Messenger) dispatch(r Receive) {
|
|
|
|
for _, entry := range r.Entry {
|
|
|
|
for _, info := range entry.Messaging {
|
|
|
|
a := m.classify(info, entry)
|
|
|
|
if a == UnknownAction {
|
|
|
|
fmt.Println("Unknown action:", info)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-04-13 12:26:31 +03:00
|
|
|
resp := &Response{
|
|
|
|
to: Recipient{info.Sender.ID},
|
|
|
|
token: m.token,
|
|
|
|
}
|
2016-04-13 12:12:23 +03:00
|
|
|
|
2016-04-13 12:26:31 +03:00
|
|
|
switch a {
|
|
|
|
case TextAction:
|
|
|
|
for _, f := range m.messageHandlers {
|
2016-04-13 12:36:38 +03:00
|
|
|
message := *info.Message
|
|
|
|
message.Sender = info.Sender
|
|
|
|
message.Recipient = info.Recipient
|
|
|
|
message.Time = time.Unix(info.Timestamp, 0)
|
|
|
|
|
|
|
|
f(message, resp)
|
2016-04-13 12:26:31 +03:00
|
|
|
}
|
|
|
|
case DeliveryAction:
|
|
|
|
for _, f := range m.deliveryHandlers {
|
2016-04-13 12:36:38 +03:00
|
|
|
f(*info.Delivery, resp)
|
2016-04-13 10:01:42 +03:00
|
|
|
}
|
2016-04-13 09:14:23 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-14 02:45:06 +03:00
|
|
|
// classify determines what type of message a webhook event is.
|
2016-04-13 09:14:23 +03:00
|
|
|
func (m *Messenger) classify(info MessageInfo, e Entry) Action {
|
|
|
|
if info.Message != nil {
|
|
|
|
return TextAction
|
2016-04-13 12:12:23 +03:00
|
|
|
} else if info.Delivery != nil {
|
|
|
|
return DeliveryAction
|
2016-04-13 09:14:23 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return UnknownAction
|
|
|
|
}
|
|
|
|
|
2016-04-14 02:45:06 +03:00
|
|
|
// newVerifyHandler returns a function which can be used to handle webhook verification
|
2016-04-13 05:03:26 +03:00
|
|
|
func newVerifyHandler(token string) func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.FormValue("hub.verify_token") == token {
|
|
|
|
fmt.Fprintln(w, r.FormValue("hub.challenge"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintln(w, "Incorrect verify token.")
|
|
|
|
}
|
|
|
|
}
|