2016-04-13 10:01:42 +03:00
package messenger
import (
"bytes"
"encoding/json"
2016-04-16 11:01:32 +03:00
"fmt"
"image"
"image/jpeg"
"io"
2017-08-01 05:57:34 +03:00
"io/ioutil"
2016-04-16 11:01:32 +03:00
"mime/multipart"
2016-04-13 10:01:42 +03:00
"net/http"
2017-08-01 05:57:34 +03:00
"net/textproto"
"strings"
2019-07-31 00:38:58 +03:00
"golang.org/x/xerrors"
2016-04-13 10:01:42 +03:00
)
2016-10-09 06:28:21 +03:00
// AttachmentType is attachment type.
type AttachmentType string
2018-02-06 05:15:36 +03:00
type MessagingType string
2018-10-29 14:10:44 +03:00
type TopElementStyle string
2018-11-13 15:38:34 +03:00
type ImageAspectRatio string
2016-10-09 06:28:21 +03:00
2016-04-13 10:01:42 +03:00
const (
2016-04-14 03:23:45 +03:00
// SendMessageURL is API endpoint for sending messages.
2018-02-06 05:15:36 +03:00
SendMessageURL = "https://graph.facebook.com/v2.11/me/messages"
2019-03-25 21:42:34 +03:00
// ThreadControlURL is the API endpoint for passing thread control.
ThreadControlURL = "https://graph.facebook.com/v2.6/me/pass_thread_control"
// InboxPageID is managed by facebook for secondary pass to inbox features: https://developers.facebook.com/docs/messenger-platform/handover-protocol/pass-thread-control
InboxPageID = 263902037430900
2016-10-09 06:28:21 +03:00
// ImageAttachment is image attachment type.
ImageAttachment AttachmentType = "image"
// AudioAttachment is audio attachment type.
AudioAttachment AttachmentType = "audio"
// VideoAttachment is video attachment type.
VideoAttachment AttachmentType = "video"
// FileAttachment is file attachment type.
FileAttachment AttachmentType = "file"
2018-02-06 05:15:36 +03:00
// ResponseType is response messaging type
ResponseType MessagingType = "RESPONSE"
// UpdateType is update messaging type
UpdateType MessagingType = "UPDATE"
// MessageTagType is message_tag messaging type
MessageTagType MessagingType = "MESSAGE_TAG"
// NonPromotionalSubscriptionType is NON_PROMOTIONAL_SUBSCRIPTION messaging type
NonPromotionalSubscriptionType MessagingType = "NON_PROMOTIONAL_SUBSCRIPTION"
2018-10-29 14:10:44 +03:00
// TopElementStyle is compact.
CompactTopElementStyle TopElementStyle = "compact"
// TopElementStyle is large.
LargeTopElementStyle TopElementStyle = "large"
2018-11-13 15:38:34 +03:00
// ImageAspectRatio is horizontal (1.91:1). Default.
HorizontalImageAspectRatio ImageAspectRatio = "horizontal"
// ImageAspectRatio is square.
SquareImageAspectRatio ImageAspectRatio = "square"
2016-04-13 10:01:42 +03:00
)
2016-10-07 16:28:27 +03:00
// QueryResponse is the response sent back by Facebook when setting up things
// like greetings or call-to-actions
type QueryResponse struct {
Error * QueryError ` json:"error,omitempty" `
Result string ` json:"result,omitempty" `
}
// QueryError is representing an error sent back by Facebook
type QueryError struct {
2019-10-14 10:58:59 +03:00
Message string ` json:"message" `
Type string ` json:"type" `
Code int ` json:"code" `
ErrorSubcode int ` json:"error_subcode" `
FBTraceID string ` json:"fbtrace_id" `
2016-10-07 16:28:27 +03:00
}
2019-07-31 00:38:58 +03:00
// QueryError implements error
func ( e QueryError ) Error ( ) string {
return e . Message
}
2016-10-07 16:28:27 +03:00
func checkFacebookError ( r io . Reader ) error {
var err error
qr := QueryResponse { }
err = json . NewDecoder ( r ) . Decode ( & qr )
2019-07-25 14:37:27 +03:00
if err != nil {
2019-07-31 00:38:58 +03:00
return xerrors . Errorf ( "json unmarshal error: %w" , err )
2019-07-25 14:37:27 +03:00
}
2016-10-07 16:28:27 +03:00
if qr . Error != nil {
2019-07-31 00:38:58 +03:00
return xerrors . Errorf ( "facebook error: %w" , qr . Error )
2016-10-07 16:28:27 +03:00
}
2017-07-31 22:24:49 +03:00
return nil
2016-10-07 16:28:27 +03:00
}
2016-04-14 03:23:45 +03:00
// Response is used for responding to events with messages.
2016-04-13 10:01:42 +03:00
type Response struct {
token string
to Recipient
}
2019-01-02 00:26:58 +03:00
// SetToken is for using DispatchMessage from outside.
func ( r * Response ) SetToken ( token string ) {
r . token = token
}
2016-04-14 03:23:45 +03:00
// Text sends a textual message.
2018-02-06 05:15:36 +03:00
func ( r * Response ) Text ( message string , messagingType MessagingType , tags ... string ) error {
return r . TextWithReplies ( message , nil , messagingType , tags ... )
2016-07-08 16:49:35 +03:00
}
// TextWithReplies sends a textual message with some replies
2018-02-06 05:15:36 +03:00
// messagingType should be one of the following: "RESPONSE","UPDATE","MESSAGE_TAG","NON_PROMOTIONAL_SUBSCRIPTION"
// only supply tags when messagingType == "MESSAGE_TAG" (see https://developers.facebook.com/docs/messenger-platform/send-messages#messaging_types for more)
func ( r * Response ) TextWithReplies ( message string , replies [ ] QuickReply , messagingType MessagingType , tags ... string ) error {
var tag string
if len ( tags ) > 0 {
tag = tags [ 0 ]
}
2016-04-13 10:01:42 +03:00
m := SendMessage {
2018-02-06 05:15:36 +03:00
MessagingType : messagingType ,
Recipient : r . to ,
2016-04-13 10:01:42 +03:00
Message : MessageData {
2016-07-08 16:49:35 +03:00
Text : message ,
2017-03-15 20:29:26 +03:00
Attachment : nil ,
QuickReplies : replies ,
} ,
2018-02-06 05:15:36 +03:00
Tag : tag ,
2017-03-15 20:29:26 +03:00
}
2017-04-09 19:58:35 +03:00
return r . DispatchMessage ( & m )
2017-03-15 20:29:26 +03:00
}
// AttachmentWithReplies sends a attachment message with some replies
2018-02-06 05:15:36 +03:00
func ( r * Response ) AttachmentWithReplies ( attachment * StructuredMessageAttachment , replies [ ] QuickReply , messagingType MessagingType , tags ... string ) error {
var tag string
if len ( tags ) > 0 {
tag = tags [ 0 ]
}
2017-03-15 20:29:26 +03:00
m := SendMessage {
2018-02-06 05:15:36 +03:00
MessagingType : messagingType ,
Recipient : r . to ,
2017-03-15 20:29:26 +03:00
Message : MessageData {
Attachment : attachment ,
2016-07-08 16:49:35 +03:00
QuickReplies : replies ,
2016-04-13 10:01:42 +03:00
} ,
2018-02-06 05:15:36 +03:00
Tag : tag ,
2016-04-13 10:01:42 +03:00
}
2017-04-09 19:58:35 +03:00
return r . DispatchMessage ( & m )
2016-04-13 10:01:42 +03:00
}
2016-04-16 11:01:32 +03:00
// Image sends an image.
func ( r * Response ) Image ( im image . Image ) error {
imageBytes := new ( bytes . Buffer )
2016-11-11 06:28:32 +03:00
err := jpeg . Encode ( imageBytes , im , nil )
2016-04-16 11:01:32 +03:00
if err != nil {
return err
}
2016-11-11 06:28:32 +03:00
return r . AttachmentData ( ImageAttachment , "meme.jpg" , imageBytes )
2016-04-16 11:01:32 +03:00
}
2016-10-09 06:28:21 +03:00
// Attachment sends an image, sound, video or a regular file to a chat.
2018-02-06 05:15:36 +03:00
func ( r * Response ) Attachment ( dataType AttachmentType , url string , messagingType MessagingType , tags ... string ) error {
var tag string
if len ( tags ) > 0 {
tag = tags [ 0 ]
}
2016-10-09 06:28:21 +03:00
m := SendStructuredMessage {
2018-02-06 05:15:36 +03:00
MessagingType : messagingType ,
Recipient : r . to ,
2016-10-09 06:28:21 +03:00
Message : StructuredMessageData {
Attachment : StructuredMessageAttachment {
Type : dataType ,
Payload : StructuredMessagePayload {
Url : url ,
} ,
} ,
} ,
2018-02-06 05:15:36 +03:00
Tag : tag ,
2016-10-09 06:28:21 +03:00
}
2017-04-09 19:58:35 +03:00
return r . DispatchMessage ( & m )
2016-10-09 06:28:21 +03:00
}
2017-08-01 05:57:34 +03:00
// copied from multipart package
var quoteEscaper = strings . NewReplacer ( "\\" , "\\\\" , ` " ` , "\\\"" )
// copied from multipart package
func escapeQuotes ( s string ) string {
return quoteEscaper . Replace ( s )
}
// copied from multipart package with slight changes due to fixed content-type there
func createFormFile ( filename string , w * multipart . Writer , contentType string ) ( io . Writer , error ) {
h := make ( textproto . MIMEHeader )
h . Set ( "Content-Disposition" ,
fmt . Sprintf ( ` form-data; name="filedata"; filename="%s" ` ,
escapeQuotes ( filename ) ) )
h . Set ( "Content-Type" , contentType )
return w . CreatePart ( h )
}
2016-11-11 06:28:32 +03:00
// AttachmentData sends an image, sound, video or a regular file to a chat via an io.Reader.
func ( r * Response ) AttachmentData ( dataType AttachmentType , filename string , filedata io . Reader ) error {
2017-08-01 05:57:34 +03:00
filedataBytes , err := ioutil . ReadAll ( filedata )
if err != nil {
return err
}
contentType := http . DetectContentType ( filedataBytes [ : 512 ] )
fmt . Println ( "Content-type detected:" , contentType )
var body bytes . Buffer
multipartWriter := multipart . NewWriter ( & body )
data , err := createFormFile ( filename , multipartWriter , contentType )
2016-11-11 06:28:32 +03:00
if err != nil {
return err
}
2017-08-01 05:57:34 +03:00
_ , err = bytes . NewBuffer ( filedataBytes ) . WriteTo ( data )
2016-11-11 06:28:32 +03:00
if err != nil {
return err
}
2017-08-01 05:57:34 +03:00
multipartWriter . WriteField ( "recipient" , fmt . Sprintf ( ` { "id":"%v"} ` , r . to . ID ) )
multipartWriter . WriteField ( "message" , fmt . Sprintf ( ` { "attachment": { "type":"%v", "payload": { }}} ` , dataType ) )
2016-11-11 06:28:32 +03:00
2017-08-01 05:57:34 +03:00
req , err := http . NewRequest ( "POST" , SendMessageURL , & body )
2016-11-11 06:28:32 +03:00
if err != nil {
return err
}
req . URL . RawQuery = "access_token=" + r . token
2017-08-01 05:57:34 +03:00
req . Header . Set ( "Content-Type" , multipartWriter . FormDataContentType ( ) )
2016-11-11 06:28:32 +03:00
client := & http . Client { }
resp , err := client . Do ( req )
if err != nil {
return err
}
2019-11-15 17:01:19 +03:00
defer resp . Body . Close ( )
2016-11-11 06:28:32 +03:00
2017-07-31 22:24:49 +03:00
return checkFacebookError ( resp . Body )
2016-11-11 06:28:32 +03:00
}
2016-05-04 11:09:34 +03:00
// ButtonTemplate sends a message with the main contents being button elements
2018-02-06 05:15:36 +03:00
func ( r * Response ) ButtonTemplate ( text string , buttons * [ ] StructuredMessageButton , messagingType MessagingType , tags ... string ) error {
var tag string
if len ( tags ) > 0 {
tag = tags [ 0 ]
}
2016-05-04 11:09:34 +03:00
m := SendStructuredMessage {
2018-02-06 05:15:36 +03:00
MessagingType : messagingType ,
Recipient : r . to ,
2016-05-04 11:09:34 +03:00
Message : StructuredMessageData {
Attachment : StructuredMessageAttachment {
Type : "template" ,
Payload : StructuredMessagePayload {
TemplateType : "button" ,
Text : text ,
Buttons : buttons ,
Elements : nil ,
} ,
} ,
2016-05-03 16:42:25 +03:00
} ,
2018-02-06 05:15:36 +03:00
Tag : tag ,
2016-05-03 16:42:25 +03:00
}
2016-05-04 11:09:34 +03:00
2017-04-09 19:58:35 +03:00
return r . DispatchMessage ( & m )
2016-05-03 16:42:25 +03:00
}
2016-05-04 11:09:34 +03:00
// GenericTemplate is a message which allows for structural elements to be sent
2018-02-06 05:15:36 +03:00
func ( r * Response ) GenericTemplate ( elements * [ ] StructuredMessageElement , messagingType MessagingType , tags ... string ) error {
var tag string
if len ( tags ) > 0 {
tag = tags [ 0 ]
}
2016-05-04 11:09:34 +03:00
m := SendStructuredMessage {
2018-02-06 05:15:36 +03:00
MessagingType : messagingType ,
Recipient : r . to ,
2016-05-04 11:09:34 +03:00
Message : StructuredMessageData {
Attachment : StructuredMessageAttachment {
Type : "template" ,
Payload : StructuredMessagePayload {
TemplateType : "generic" ,
Buttons : nil ,
Elements : elements ,
} ,
} ,
2016-05-03 16:42:25 +03:00
} ,
2018-02-06 05:15:36 +03:00
Tag : tag ,
2016-05-03 16:42:25 +03:00
}
2017-04-09 19:58:35 +03:00
return r . DispatchMessage ( & m )
2016-09-08 10:55:48 +03:00
}
2019-04-14 13:28:09 +03:00
// ListTemplate sends a list of elements
func ( r * Response ) ListTemplate ( elements * [ ] StructuredMessageElement , messagingType MessagingType , tags ... string ) error {
var tag string
if len ( tags ) > 0 {
tag = tags [ 0 ]
}
m := SendStructuredMessage {
MessagingType : messagingType ,
Recipient : r . to ,
Message : StructuredMessageData {
Attachment : StructuredMessageAttachment {
Type : "template" ,
Payload : StructuredMessagePayload {
TopElementStyle : "compact" ,
TemplateType : "list" ,
Buttons : nil ,
Elements : elements ,
} ,
} ,
} ,
Tag : tag ,
}
return r . DispatchMessage ( & m )
}
2016-09-08 10:55:48 +03:00
// SenderAction sends a info about sender action
func ( r * Response ) SenderAction ( action string ) error {
m := SendSenderAction {
Recipient : r . to ,
SenderAction : action ,
}
2017-04-09 19:58:35 +03:00
return r . DispatchMessage ( & m )
}
2016-09-08 10:55:48 +03:00
2017-04-09 19:58:35 +03:00
// DispatchMessage posts the message to messenger, return the error if there's any
func ( r * Response ) DispatchMessage ( m interface { } ) error {
2016-09-08 10:55:48 +03:00
data , err := json . Marshal ( m )
if err != nil {
return err
2016-05-03 16:42:25 +03:00
}
req , err := http . NewRequest ( "POST" , SendMessageURL , bytes . NewBuffer ( data ) )
if err != nil {
return err
}
req . Header . Set ( "Content-Type" , "application/json" )
req . URL . RawQuery = "access_token=" + r . token
2017-04-09 19:58:35 +03:00
resp , err := http . DefaultClient . Do ( req )
2016-10-07 16:28:27 +03:00
if err != nil {
return err
}
2016-05-03 16:42:25 +03:00
defer resp . Body . Close ( )
2017-04-09 19:58:35 +03:00
if resp . StatusCode == 200 {
return nil
}
2016-10-07 16:28:27 +03:00
return checkFacebookError ( resp . Body )
2016-05-03 16:42:25 +03:00
}
2019-03-25 21:42:34 +03:00
// PassThreadToInbox Uses Messenger Handover Protocol for live inbox
// https://developers.facebook.com/docs/messenger-platform/handover-protocol/#inbox
func ( r * Response ) PassThreadToInbox ( ) error {
p := passThreadControl {
Recipient : r . to ,
TargetAppID : InboxPageID ,
Metadata : "Passing to inbox secondary app" ,
}
data , err := json . Marshal ( p )
if err != nil {
return err
}
req , err := http . NewRequest ( "POST" , ThreadControlURL , bytes . NewBuffer ( data ) )
if err != nil {
return err
}
req . Header . Set ( "Content-Type" , "application/json" )
req . URL . RawQuery = "access_token=" + r . token
resp , err := http . DefaultClient . Do ( req )
if err != nil {
return err
}
defer resp . Body . Close ( )
return checkFacebookError ( resp . Body )
}
2016-04-14 03:23:45 +03:00
// SendMessage is the information sent in an API request to Facebook.
2016-04-13 10:01:42 +03:00
type SendMessage struct {
2018-02-06 05:15:36 +03:00
MessagingType MessagingType ` json:"messaging_type" `
Recipient Recipient ` json:"recipient" `
Message MessageData ` json:"message" `
Tag string ` json:"tag,omitempty" `
2016-04-13 10:01:42 +03:00
}
2017-03-15 20:29:26 +03:00
// MessageData is a message consisting of text or an attachment, with an additional selection of optional quick replies.
2016-04-13 10:01:42 +03:00
type MessageData struct {
2017-03-15 20:29:26 +03:00
Text string ` json:"text,omitempty" `
Attachment * StructuredMessageAttachment ` json:"attachment,omitempty" `
QuickReplies [ ] QuickReply ` json:"quick_replies,omitempty" `
2016-04-13 10:01:42 +03:00
}
2016-05-03 16:42:25 +03:00
2016-05-04 11:09:34 +03:00
// SendStructuredMessage is a structured message template.
2016-05-03 16:42:25 +03:00
type SendStructuredMessage struct {
2018-02-06 05:15:36 +03:00
MessagingType MessagingType ` json:"messaging_type" `
Recipient Recipient ` json:"recipient" `
Message StructuredMessageData ` json:"message" `
Tag string ` json:"tag,omitempty" `
2016-05-03 16:42:25 +03:00
}
2016-05-04 11:09:34 +03:00
// StructuredMessageData is an attachment sent with a structured message.
2016-05-03 16:42:25 +03:00
type StructuredMessageData struct {
Attachment StructuredMessageAttachment ` json:"attachment" `
}
2016-05-04 11:09:34 +03:00
// StructuredMessageAttachment is the attachment of a structured message.
2016-05-03 16:42:25 +03:00
type StructuredMessageAttachment struct {
2016-05-04 11:09:34 +03:00
// Type must be template
2019-02-22 19:16:14 +03:00
Title string ` json:"title,omitempty" `
URL string ` json:"url,omitempty" `
Type AttachmentType ` json:"type" `
2016-05-03 16:42:25 +03:00
// Payload is the information for the file which was sent in the attachment.
Payload StructuredMessagePayload ` json:"payload" `
}
2016-05-04 11:09:34 +03:00
// StructuredMessagePayload is the actual payload of an attachment
2016-05-03 16:42:25 +03:00
type StructuredMessagePayload struct {
2016-05-04 11:09:34 +03:00
// TemplateType must be button, generic or receipt
2018-11-13 15:38:34 +03:00
TemplateType string ` json:"template_type,omitempty" `
TopElementStyle TopElementStyle ` json:"top_element_style,omitempty" `
Text string ` json:"text,omitempty" `
ImageAspectRatio ImageAspectRatio ` json:"image_aspect_ratio,omitempty" `
2018-11-13 16:00:30 +03:00
Sharable bool ` json:"sharable,omitempty" `
2018-11-13 15:38:34 +03:00
Elements * [ ] StructuredMessageElement ` json:"elements,omitempty" `
Buttons * [ ] StructuredMessageButton ` json:"buttons,omitempty" `
Url string ` json:"url,omitempty" `
2019-02-26 01:52:06 +03:00
AttachmentID string ` json:"attachment_id,omitempty" `
2016-05-03 16:42:25 +03:00
}
2016-05-04 11:09:34 +03:00
// StructuredMessageElement is a response containing structural elements
2016-05-03 16:42:25 +03:00
type StructuredMessageElement struct {
2018-11-13 16:41:30 +03:00
Title string ` json:"title" `
ImageURL string ` json:"image_url" `
2019-01-02 00:13:23 +03:00
ItemURL string ` json:"item_url,omitempty" `
2018-11-13 16:41:30 +03:00
Subtitle string ` json:"subtitle" `
DefaultAction * DefaultAction ` json:"default_action,omitempty" `
Buttons [ ] StructuredMessageButton ` json:"buttons" `
}
// DefaultAction is a response containing default action properties
type DefaultAction struct {
Type string ` json:"type" `
URL string ` json:"url,omitempty" `
WebviewHeightRatio string ` json:"webview_height_ratio,omitempty" `
MessengerExtensions bool ` json:"messenger_extensions,omitempty" `
FallbackURL string ` json:"fallback_url,omitempty" `
WebviewShareButton string ` json:"webview_share_button,omitempty" `
2016-05-03 16:42:25 +03:00
}
2016-05-04 11:09:34 +03:00
// StructuredMessageButton is a response containing buttons
2016-05-03 16:42:25 +03:00
type StructuredMessageButton struct {
2019-02-27 01:07:06 +03:00
Type string ` json:"type" `
URL string ` json:"url,omitempty" `
Title string ` json:"title,omitempty" `
Payload string ` json:"payload,omitempty" `
WebviewHeightRatio string ` json:"webview_height_ratio,omitempty" `
MessengerExtensions bool ` json:"messenger_extensions,omitempty" `
FallbackURL string ` json:"fallback_url,omitempty" `
WebviewShareButton string ` json:"webview_share_button,omitempty" `
ShareContents * StructuredMessageData ` json:"share_contents,omitempty" `
2016-09-08 10:55:48 +03:00
}
// SendSenderAction is the information about sender action
type SendSenderAction struct {
Recipient Recipient ` json:"recipient" `
SenderAction string ` json:"sender_action" `
2016-05-03 16:42:25 +03:00
}