1
0
mirror of synced 2024-11-22 04:46:05 +03:00

Messenger downstream patches (#1)

* add processing response from facebook
* add receipt message payload
* Restore checkFacebookError
* add metadata in MessageData
* improve optional ProfileFields
* Fix conflict with receiving.go while cherry-picking
* Add title to payload
* Move to Github Actions & new package name
* fix for example
* remove limit for parallel runs
This commit is contained in:
Pavel 2021-02-09 11:14:42 +03:00 committed by GitHub
parent 14c1f126e4
commit 94f238346c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 180 additions and 94 deletions

35
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: ci
on:
push:
branches:
- '**'
tags-ignore:
- '*.*'
pull_request:
env:
GO111MODULE: on
jobs:
tests:
name: Tests
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.11', '1.12', '1.13', '1.14', '1.15']
steps:
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Get dependencies
run: go mod tidy
- name: Tests
run: |
go test -v ./...
go build ./examples/...
- name: Coverage
run: bash <(curl -s https://codecov.io/bash)

1
.gitignore vendored
View File

@ -6,6 +6,7 @@
# Folders # Folders
_obj _obj
_test _test
.idea
# Architecture specific extensions/prefixes # Architecture specific extensions/prefixes
*.[568vq] *.[568vq]

View File

@ -1,11 +0,0 @@
language: go
go:
- 1.10.x
- master
go_import_path: github.com/paked/messenger
install: go get -t ./...
script:
- go test -v ./...
- go build ./examples/...

View File

@ -1,10 +1,14 @@
# Messenger [![GoDoc](https://godoc.org/github.com/paked/messenger?status.svg)](https://godoc.org/github.com/paked/messenger) [![Build Status](https://travis-ci.org/paked/messenger.svg?branch=master)](https://travis-ci.org/paked/messenger) # Messenger
[![Build Status](https://github.com/retailcrm/messenger/workflows/ci/badge.svg)](https://github.com/retailcrm/messenger/actions)
[![Coverage](https://img.shields.io/codecov/c/gh/retailcrm/messenger/master.svg?logo=codecov&logoColor=white)](https://codecov.io/gh/retailcrm/messenger)
[![GitHub release](https://img.shields.io/github/release/retailcrm/messenger.svg?logo=github&logoColor=white)](https://github.com/retailcrm/messenger/releases)
[![Go Report Card](https://goreportcard.com/badge/github.com/retailcrm/messenger)](https://goreportcard.com/report/github.com/retailcrm/messenger)
[![GoLang version](https://img.shields.io/badge/go->=1.11-blue.svg?logo=go&logoColor=white)](https://golang.org/dl/)
[![pkg.go.dev](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white)](https://pkg.go.dev/github.com/retailcrm/messenger)
This is a Go library for making bots to be used on Facebook messenger. It is built on the [Messenger Platform](https://developers.facebook.com/docs/messenger-platform). One of the main goals of the project is to implement it in an idiomatic and easy to use fashion. This is a Go library for making bots to be used on Facebook messenger. It is built on the [Messenger Platform](https://developers.facebook.com/docs/messenger-platform). One of the main goals of the project is to implement it in an idiomatic and easy to use fashion.
You can find [examples for this library here](https://github.com/paked/messenger/blob/master/examples/). You can find [examples for this library here](https://github.com/retailcrm/messenger/blob/master/examples/).
We tag our releases Semver style.
## Tips ## Tips
@ -12,28 +16,9 @@ We tag our releases Semver style.
- You need a Facebook development app, and a Facebook page in order to build things. - You need a Facebook development app, and a Facebook page in order to build things.
- Use [ngrok](https://ngrok.com) to tunnel your locally running bot so that Facebook can reach the webhook. - Use [ngrok](https://ngrok.com) to tunnel your locally running bot so that Facebook can reach the webhook.
## Breaking Changes
In January 2019 we began tagging releases so that the package could be used properly with Go modules. Prior to that we simply maintained the following list to help users migrate between versions, it's staying here for legacy reasons. From now on, however, you should find breaking changes in the notes of a new release.
`paked/messenger` is a pretty stable library, however, changes will be made which might break backwards compatibility. For the convenience of its users, these are documented here.
- 06/2/18: Added messaging_type field for message send API request as it is required by FB
- [23/1/17](https://github.com/paked/messenger/commit/1145fe35249f8ce14d3c0a52544e4a4babdc15a4): Updating timezone type to `float64` in profile struct
- [12/9/16](https://github.com/paked/messenger/commit/47f193fc858e2d710c061e88b12dbd804a399e57): Removing unused parameter `text string` from function `(r *Response) GenericTemplate`.
- [20/5/16](https://github.com/paked/messenger/commit/1dc4bcc67dec50e2f58436ffbc7d61ca9da5b943): Leaving the `WebhookURL` field blank in `Options` will yield a URL of "/" instead of a panic.
- [4/5/16](https://github.com/paked/messenger/commit/eb0e72a5dcd3bfaffcfe88dced6d6ac5247f9da1): The URL to use for the webhook is changable in the `Options` struct.
## Inspiration
Messenger takes design cues from:
- [`net/http`](https://godoc.org/net/http)
- [`github.com/nickvanw/ircx`](https://github.com/nickvanw/ircx)
## Projects ## Projects
This is a list of projects use `messenger`. If you would like to add your own, submit a [Pull Request](https://github.com/paked/messenger/pulls/new) adding it below. This is a list of projects use `messenger`. If you would like to add your own, submit a [Pull Request](https://github.com/retailcrm/messenger/pulls/new) adding it below.
- [meme-maker](https://github.com/paked/meme-maker) by @paked: A bot which, given a photo and a caption, will create a macro meme. - [meme-maker](https://github.com/paked/meme-maker) by @paked: A bot which, given a photo and a caption, will create a macro meme.
- [drone-facebook](https://github.com/appleboy/drone-facebook) by @appleboy: [Drone.io](https://drone.io) plugin which sends Facebook notifications - [drone-facebook](https://github.com/appleboy/drone-facebook) by @appleboy: [Drone.io](https://drone.io) plugin which sends Facebook notifications

View File

@ -8,7 +8,7 @@ import (
"os" "os"
"time" "time"
"github.com/paked/messenger" "github.com/retailcrm/messenger"
) )
var ( var (
@ -48,7 +48,7 @@ func main() {
fmt.Println("Something went wrong!", err) fmt.Println("Something went wrong!", err)
} }
r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), messenger.ResponseType) r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), messenger.ResponseType, "")
}) })
// Setup a handler to be triggered when a message is delivered // Setup a handler to be triggered when a message is delivered

View File

@ -8,7 +8,7 @@ import (
"os" "os"
"time" "time"
"github.com/paked/messenger" "github.com/retailcrm/messenger"
) )
var ( var (
@ -57,7 +57,7 @@ func main() {
fmt.Println("Something went wrong!", err) fmt.Println("Something went wrong!", err)
} }
r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), messenger.ResponseType) r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), messenger.ResponseType, "")
}) })
addr := fmt.Sprintf("%s:%d", *host, *port) addr := fmt.Sprintf("%s:%d", *host, *port)

View File

@ -11,7 +11,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/paked/messenger" "github.com/retailcrm/messenger"
) )
const ( const (
@ -84,7 +84,7 @@ func main() {
text = "You've been logged out of your account." text = "You've been logged out of your account."
} }
if err := r.Text(text, messenger.ResponseType); err != nil { if _, err := r.Text(text, messenger.ResponseType, ""); err != nil {
log.Println("Failed to send account linking feedback") log.Println("Failed to send account linking feedback")
} }
}) })
@ -116,7 +116,8 @@ func loginButton(r *messenger.Response) error {
URL: "https://" + path.Join(*publicHost, loginPath), URL: "https://" + path.Join(*publicHost, loginPath),
}, },
} }
return r.ButtonTemplate("Link your account.", buttons, messenger.ResponseType) _, err := r.ButtonTemplate("Link your account.", buttons, messenger.ResponseType, "")
return err
} }
// logoutButton show to the user a button that can be used to start // logoutButton show to the user a button that can be used to start
@ -127,12 +128,14 @@ func logoutButton(r *messenger.Response) error {
Type: "account_unlink", Type: "account_unlink",
}, },
} }
return r.ButtonTemplate("Unlink your account.", buttons, messenger.ResponseType) _, err := r.ButtonTemplate("Unlink your account.", buttons, messenger.ResponseType, "")
return err
} }
// greeting salutes the user. // greeting salutes the user.
func greeting(p messenger.Profile, r *messenger.Response) error { func greeting(p messenger.Profile, r *messenger.Response) error {
return r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), messenger.ResponseType) _, err := r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), messenger.ResponseType, "")
return err
} }
// help displays possibles actions to the user. // help displays possibles actions to the user.
@ -153,7 +156,8 @@ func help(p messenger.Profile, r *messenger.Response) error {
}, },
} }
return r.TextWithReplies(text, replies, messenger.ResponseType) _, err := r.TextWithReplies(text, replies, messenger.ResponseType, "")
return err
} }
// loginForm is the endpoint responsible to displays a login // loginForm is the endpoint responsible to displays a login

2
go.mod
View File

@ -1,4 +1,4 @@
module github.com/paked/messenger module github.com/retailcrm/messenger
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect

View File

@ -16,6 +16,8 @@ type Message struct {
// Message is mine // Message is mine
IsEcho bool `json:"is_echo,omitempty"` IsEcho bool `json:"is_echo,omitempty"`
// Mid is the ID of the message. // Mid is the ID of the message.
Metadata string `json:"metadata"`
// Mid is the ID of the message.
Mid string `json:"mid"` Mid string `json:"mid"`
// Seq is order the message was sent in relation to other messages. // Seq is order the message was sent in relation to other messages.
Seq int `json:"seq"` Seq int `json:"seq"`

View File

@ -18,6 +18,10 @@ const (
// ProfileURL is the API endpoint used for retrieving profiles. // ProfileURL is the API endpoint used for retrieving profiles.
// Used in the form: https://graph.facebook.com/v2.6/<USER_ID>?fields=<PROFILE_FIELDS>&access_token=<PAGE_ACCESS_TOKEN> // Used in the form: https://graph.facebook.com/v2.6/<USER_ID>?fields=<PROFILE_FIELDS>&access_token=<PAGE_ACCESS_TOKEN>
ProfileURL = "https://graph.facebook.com/v2.6/" ProfileURL = "https://graph.facebook.com/v2.6/"
// ProfileFields is a list of JSON field names which will be populated by the profile query.
ProfileFields = "first_name,last_name,profile_pic"
// SendSettingsURL is API endpoint for saving settings. // SendSettingsURL is API endpoint for saving settings.
SendSettingsURL = "https://graph.facebook.com/v2.6/me/thread_settings" SendSettingsURL = "https://graph.facebook.com/v2.6/me/thread_settings"
@ -170,7 +174,6 @@ func (m *Messenger) ProfileByID(id int64, profileFields []string) (Profile, erro
} }
fields := strings.Join(profileFields, ",") fields := strings.Join(profileFields, ",")
req.URL.RawQuery = "fields=" + fields + "&access_token=" + m.token req.URL.RawQuery = "fields=" + fields + "&access_token=" + m.token
client := &http.Client{} client := &http.Client{}
@ -202,7 +205,9 @@ func (m *Messenger) ProfileByID(id int64, profileFields []string) (Profile, erro
} }
// GreetingSetting sends settings for greeting // GreetingSetting sends settings for greeting
func (m *Messenger) GreetingSetting(text string) error { func (m *Messenger) GreetingSetting(text string) (QueryResponse, error) {
var qr QueryResponse
d := GreetingSetting{ d := GreetingSetting{
SettingType: "greeting", SettingType: "greeting",
Greeting: GreetingInfo{ Greeting: GreetingInfo{
@ -212,12 +217,12 @@ func (m *Messenger) GreetingSetting(text string) error {
data, err := json.Marshal(d) data, err := json.Marshal(d)
if err != nil { if err != nil {
return err return qr, err
} }
req, err := http.NewRequest("POST", SendSettingsURL, bytes.NewBuffer(data)) req, err := http.NewRequest("POST", SendSettingsURL, bytes.NewBuffer(data))
if err != nil { if err != nil {
return err return qr, err
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
@ -227,15 +232,17 @@ func (m *Messenger) GreetingSetting(text string) error {
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return err return qr, err
} }
defer resp.Body.Close() defer resp.Body.Close()
return checkFacebookError(resp.Body) return getFacebookQueryResponse(resp.Body)
} }
// CallToActionsSetting sends settings for Get Started or Persistent Menu // CallToActionsSetting sends settings for Get Started or Persistent Menu
func (m *Messenger) CallToActionsSetting(state string, actions []CallToActionsItem) error { func (m *Messenger) CallToActionsSetting(state string, actions []CallToActionsItem) (QueryResponse, error) {
var qr QueryResponse
d := CallToActionsSetting{ d := CallToActionsSetting{
SettingType: "call_to_actions", SettingType: "call_to_actions",
ThreadState: state, ThreadState: state,
@ -244,12 +251,12 @@ func (m *Messenger) CallToActionsSetting(state string, actions []CallToActionsIt
data, err := json.Marshal(d) data, err := json.Marshal(d)
if err != nil { if err != nil {
return err return qr, err
} }
req, err := http.NewRequest("POST", SendSettingsURL, bytes.NewBuffer(data)) req, err := http.NewRequest("POST", SendSettingsURL, bytes.NewBuffer(data))
if err != nil { if err != nil {
return err return qr, err
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
@ -259,11 +266,11 @@ func (m *Messenger) CallToActionsSetting(state string, actions []CallToActionsIt
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return err return qr, err
} }
defer resp.Body.Close() defer resp.Body.Close()
return checkFacebookError(resp.Body) return getFacebookQueryResponse(resp.Body)
} }
// handle is the internal HTTP handler for the webhooks. // handle is the internal HTTP handler for the webhooks.
@ -426,37 +433,37 @@ func (m *Messenger) Response(to int64) *Response {
} }
// Send will send a textual message to a user. This user must have previously initiated a conversation with the bot. // Send will send a textual message to a user. This user must have previously initiated a conversation with the bot.
func (m *Messenger) Send(to Recipient, message string, messagingType MessagingType, tags ...string) error { func (m *Messenger) Send(to Recipient, message string, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) {
return m.SendWithReplies(to, message, nil, messagingType, tags...) return m.SendWithReplies(to, message, nil, messagingType, metadata, tags...)
} }
// SendGeneralMessage will send the GenericTemplate message // SendGeneralMessage will send the GenericTemplate message
func (m *Messenger) SendGeneralMessage(to Recipient, elements *[]StructuredMessageElement, messagingType MessagingType, tags ...string) error { func (m *Messenger) SendGeneralMessage(to Recipient, elements *[]StructuredMessageElement, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) {
r := &Response{ r := &Response{
token: m.token, token: m.token,
to: to, to: to,
} }
return r.GenericTemplate(elements, messagingType, tags...) return r.GenericTemplate(elements, messagingType, metadata, tags...)
} }
// SendWithReplies sends a textual message to a user, but gives them the option of numerous quick response options. // SendWithReplies sends a textual message to a user, but gives them the option of numerous quick response options.
func (m *Messenger) SendWithReplies(to Recipient, message string, replies []QuickReply, messagingType MessagingType, tags ...string) error { func (m *Messenger) SendWithReplies(to Recipient, message string, replies []QuickReply, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) {
response := &Response{ response := &Response{
token: m.token, token: m.token,
to: to, to: to,
} }
return response.TextWithReplies(message, replies, messagingType, tags...) return response.TextWithReplies(message, replies, messagingType, metadata, tags...)
} }
// Attachment sends an image, sound, video or a regular file to a given recipient. // Attachment sends an image, sound, video or a regular file to a given recipient.
func (m *Messenger) Attachment(to Recipient, dataType AttachmentType, url string, messagingType MessagingType, tags ...string) error { func (m *Messenger) Attachment(to Recipient, dataType AttachmentType, url string, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) {
response := &Response{ response := &Response{
token: m.token, token: m.token,
to: to, to: to,
} }
return response.Attachment(dataType, url, messagingType, tags...) return response.Attachment(dataType, url, messagingType, metadata, tags...)
} }
// EnableChatExtension set the homepage url required for a chat extension. // EnableChatExtension set the homepage url required for a chat extension.

View File

@ -112,8 +112,8 @@ type QuickReply struct {
// Payload is the information on where an attachment is. // Payload is the information on where an attachment is.
type Payload struct { type Payload struct {
// URL is where the attachment resides on the internet.
URL string `json:"url,omitempty"` URL string `json:"url,omitempty"`
Title string `json:"title,omitempty"`
// Coordinates is Lat/Long pair of location pin // Coordinates is Lat/Long pair of location pin
Coordinates *Coordinates `json:"coordinates,omitempty"` Coordinates *Coordinates `json:"coordinates,omitempty"`
} }

View File

@ -62,8 +62,9 @@ const (
// QueryResponse is the response sent back by Facebook when setting up things // QueryResponse is the response sent back by Facebook when setting up things
// like greetings or call-to-actions // like greetings or call-to-actions
type QueryResponse struct { type QueryResponse struct {
Error *QueryError `json:"error,omitempty"` Error *QueryError `json:"error,omitempty"`
Result string `json:"result,omitempty"` RecipientID string `json:"recipient_id"`
MessageID string `json:"message_id"`
} }
// QueryError is representing an error sent back by Facebook // QueryError is representing an error sent back by Facebook
@ -95,6 +96,18 @@ func checkFacebookError(r io.Reader) error {
return nil return nil
} }
func getFacebookQueryResponse(r io.Reader) (QueryResponse, error) {
qr := QueryResponse{}
err := json.NewDecoder(r).Decode(&qr)
if err != nil {
return qr, xerrors.Errorf("json unmarshal error: %w", err)
}
if qr.Error != nil {
return qr, xerrors.Errorf("facebook error: %w", qr.Error)
}
return qr, nil
}
// Response is used for responding to events with messages. // Response is used for responding to events with messages.
type Response struct { type Response struct {
token string token string
@ -107,14 +120,14 @@ func (r *Response) SetToken(token string) {
} }
// Text sends a textual message. // Text sends a textual message.
func (r *Response) Text(message string, messagingType MessagingType, tags ...string) error { func (r *Response) Text(message string, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) {
return r.TextWithReplies(message, nil, messagingType, tags...) return r.TextWithReplies(message, nil, messagingType, metadata, tags...)
} }
// TextWithReplies sends a textual message with some replies // TextWithReplies sends a textual message with some replies
// messagingType should be one of the following: "RESPONSE","UPDATE","MESSAGE_TAG","NON_PROMOTIONAL_SUBSCRIPTION" // 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) // 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 { func (r *Response) TextWithReplies(message string, replies []QuickReply, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) {
var tag string var tag string
if len(tags) > 0 { if len(tags) > 0 {
tag = tags[0] tag = tags[0]
@ -127,6 +140,7 @@ func (r *Response) TextWithReplies(message string, replies []QuickReply, messagi
Text: message, Text: message,
Attachment: nil, Attachment: nil,
QuickReplies: replies, QuickReplies: replies,
Metadata: metadata,
}, },
Tag: tag, Tag: tag,
} }
@ -134,7 +148,7 @@ func (r *Response) TextWithReplies(message string, replies []QuickReply, messagi
} }
// AttachmentWithReplies sends a attachment message with some replies // AttachmentWithReplies sends a attachment message with some replies
func (r *Response) AttachmentWithReplies(attachment *StructuredMessageAttachment, replies []QuickReply, messagingType MessagingType, tags ...string) error { func (r *Response) AttachmentWithReplies(attachment *StructuredMessageAttachment, replies []QuickReply, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) {
var tag string var tag string
if len(tags) > 0 { if len(tags) > 0 {
tag = tags[0] tag = tags[0]
@ -146,6 +160,7 @@ func (r *Response) AttachmentWithReplies(attachment *StructuredMessageAttachment
Message: MessageData{ Message: MessageData{
Attachment: attachment, Attachment: attachment,
QuickReplies: replies, QuickReplies: replies,
Metadata: metadata,
}, },
Tag: tag, Tag: tag,
} }
@ -153,18 +168,20 @@ func (r *Response) AttachmentWithReplies(attachment *StructuredMessageAttachment
} }
// Image sends an image. // Image sends an image.
func (r *Response) Image(im image.Image) error { func (r *Response) Image(im image.Image) (QueryResponse, error) {
var qr QueryResponse
imageBytes := new(bytes.Buffer) imageBytes := new(bytes.Buffer)
err := jpeg.Encode(imageBytes, im, nil) err := jpeg.Encode(imageBytes, im, nil)
if err != nil { if err != nil {
return err return qr, err
} }
return r.AttachmentData(ImageAttachment, "meme.jpg", imageBytes) return r.AttachmentData(ImageAttachment, "meme.jpg", imageBytes)
} }
// Attachment sends an image, sound, video or a regular file to a chat. // Attachment sends an image, sound, video or a regular file to a chat.
func (r *Response) Attachment(dataType AttachmentType, url string, messagingType MessagingType, tags ...string) error { func (r *Response) Attachment(dataType AttachmentType, url string, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) {
var tag string var tag string
if len(tags) > 0 { if len(tags) > 0 {
tag = tags[0] tag = tags[0]
@ -174,6 +191,7 @@ func (r *Response) Attachment(dataType AttachmentType, url string, messagingType
MessagingType: messagingType, MessagingType: messagingType,
Recipient: r.to, Recipient: r.to,
Message: StructuredMessageData{ Message: StructuredMessageData{
Metadata: metadata,
Attachment: StructuredMessageAttachment{ Attachment: StructuredMessageAttachment{
Type: dataType, Type: dataType,
Payload: StructuredMessagePayload{ Payload: StructuredMessagePayload{
@ -205,11 +223,12 @@ func createFormFile(filename string, w *multipart.Writer, contentType string) (i
} }
// AttachmentData sends an image, sound, video or a regular file to a chat via an io.Reader. // 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 { func (r *Response) AttachmentData(dataType AttachmentType, filename string, filedata io.Reader) (QueryResponse, error) {
var qr QueryResponse
filedataBytes, err := ioutil.ReadAll(filedata) filedataBytes, err := ioutil.ReadAll(filedata)
if err != nil { if err != nil {
return err return qr, err
} }
contentType := http.DetectContentType(filedataBytes[:512]) contentType := http.DetectContentType(filedataBytes[:512])
fmt.Println("Content-type detected:", contentType) fmt.Println("Content-type detected:", contentType)
@ -218,12 +237,12 @@ func (r *Response) AttachmentData(dataType AttachmentType, filename string, file
multipartWriter := multipart.NewWriter(&body) multipartWriter := multipart.NewWriter(&body)
data, err := createFormFile(filename, multipartWriter, contentType) data, err := createFormFile(filename, multipartWriter, contentType)
if err != nil { if err != nil {
return err return qr, err
} }
_, err = bytes.NewBuffer(filedataBytes).WriteTo(data) _, err = bytes.NewBuffer(filedataBytes).WriteTo(data)
if err != nil { if err != nil {
return err return qr, err
} }
multipartWriter.WriteField("recipient", fmt.Sprintf(`{"id":"%v"}`, r.to.ID)) multipartWriter.WriteField("recipient", fmt.Sprintf(`{"id":"%v"}`, r.to.ID))
@ -231,7 +250,7 @@ func (r *Response) AttachmentData(dataType AttachmentType, filename string, file
req, err := http.NewRequest("POST", SendMessageURL, &body) req, err := http.NewRequest("POST", SendMessageURL, &body)
if err != nil { if err != nil {
return err return qr, err
} }
req.URL.RawQuery = "access_token=" + r.token req.URL.RawQuery = "access_token=" + r.token
@ -241,15 +260,15 @@ func (r *Response) AttachmentData(dataType AttachmentType, filename string, file
client := &http.Client{} client := &http.Client{}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return err return qr, err
} }
defer resp.Body.Close() defer resp.Body.Close()
return checkFacebookError(resp.Body) return getFacebookQueryResponse(resp.Body)
} }
// ButtonTemplate sends a message with the main contents being button elements // ButtonTemplate sends a message with the main contents being button elements
func (r *Response) ButtonTemplate(text string, buttons *[]StructuredMessageButton, messagingType MessagingType, tags ...string) error { func (r *Response) ButtonTemplate(text string, buttons *[]StructuredMessageButton, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) {
var tag string var tag string
if len(tags) > 0 { if len(tags) > 0 {
tag = tags[0] tag = tags[0]
@ -259,6 +278,7 @@ func (r *Response) ButtonTemplate(text string, buttons *[]StructuredMessageButto
MessagingType: messagingType, MessagingType: messagingType,
Recipient: r.to, Recipient: r.to,
Message: StructuredMessageData{ Message: StructuredMessageData{
Metadata: metadata,
Attachment: StructuredMessageAttachment{ Attachment: StructuredMessageAttachment{
Type: "template", Type: "template",
Payload: StructuredMessagePayload{ Payload: StructuredMessagePayload{
@ -276,7 +296,7 @@ func (r *Response) ButtonTemplate(text string, buttons *[]StructuredMessageButto
} }
// GenericTemplate is a message which allows for structural elements to be sent // GenericTemplate is a message which allows for structural elements to be sent
func (r *Response) GenericTemplate(elements *[]StructuredMessageElement, messagingType MessagingType, tags ...string) error { func (r *Response) GenericTemplate(elements *[]StructuredMessageElement, messagingType MessagingType, metadata string, tags ...string) (QueryResponse, error) {
var tag string var tag string
if len(tags) > 0 { if len(tags) > 0 {
tag = tags[0] tag = tags[0]
@ -286,6 +306,7 @@ func (r *Response) GenericTemplate(elements *[]StructuredMessageElement, messagi
MessagingType: messagingType, MessagingType: messagingType,
Recipient: r.to, Recipient: r.to,
Message: StructuredMessageData{ Message: StructuredMessageData{
Metadata: metadata,
Attachment: StructuredMessageAttachment{ Attachment: StructuredMessageAttachment{
Type: "template", Type: "template",
Payload: StructuredMessagePayload{ Payload: StructuredMessagePayload{
@ -301,7 +322,7 @@ func (r *Response) GenericTemplate(elements *[]StructuredMessageElement, messagi
} }
// ListTemplate sends a list of elements // ListTemplate sends a list of elements
func (r *Response) ListTemplate(elements *[]StructuredMessageElement, messagingType MessagingType, tags ...string) error { func (r *Response) ListTemplate(elements *[]StructuredMessageElement, messagingType MessagingType, tags ...string) (QueryResponse, error) {
var tag string var tag string
if len(tags) > 0 { if len(tags) > 0 {
tag = tags[0] tag = tags[0]
@ -327,7 +348,7 @@ func (r *Response) ListTemplate(elements *[]StructuredMessageElement, messagingT
} }
// SenderAction sends a info about sender action // SenderAction sends a info about sender action
func (r *Response) SenderAction(action string) error { func (r *Response) SenderAction(action string) (QueryResponse, error) {
m := SendSenderAction{ m := SendSenderAction{
Recipient: r.to, Recipient: r.to,
SenderAction: action, SenderAction: action,
@ -336,15 +357,16 @@ func (r *Response) SenderAction(action string) error {
} }
// DispatchMessage posts the message to messenger, return the error if there's any // DispatchMessage posts the message to messenger, return the error if there's any
func (r *Response) DispatchMessage(m interface{}) error { func (r *Response) DispatchMessage(m interface{}) (QueryResponse, error) {
var res QueryResponse
data, err := json.Marshal(m) data, err := json.Marshal(m)
if err != nil { if err != nil {
return err return res, err
} }
req, err := http.NewRequest("POST", SendMessageURL, bytes.NewBuffer(data)) req, err := http.NewRequest("POST", SendMessageURL, bytes.NewBuffer(data))
if err != nil { if err != nil {
return err return res, err
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
@ -352,13 +374,12 @@ func (r *Response) DispatchMessage(m interface{}) error {
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return err return res, err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode == 200 {
return nil return getFacebookQueryResponse(resp.Body)
}
return checkFacebookError(resp.Body)
} }
// PassThreadToInbox Uses Messenger Handover Protocol for live inbox // PassThreadToInbox Uses Messenger Handover Protocol for live inbox
@ -405,6 +426,7 @@ type MessageData struct {
Text string `json:"text,omitempty"` Text string `json:"text,omitempty"`
Attachment *StructuredMessageAttachment `json:"attachment,omitempty"` Attachment *StructuredMessageAttachment `json:"attachment,omitempty"`
QuickReplies []QuickReply `json:"quick_replies,omitempty"` QuickReplies []QuickReply `json:"quick_replies,omitempty"`
Metadata string `json:"metadata,omitempty"`
} }
// SendStructuredMessage is a structured message template. // SendStructuredMessage is a structured message template.
@ -418,6 +440,7 @@ type SendStructuredMessage struct {
// StructuredMessageData is an attachment sent with a structured message. // StructuredMessageData is an attachment sent with a structured message.
type StructuredMessageData struct { type StructuredMessageData struct {
Attachment StructuredMessageAttachment `json:"attachment"` Attachment StructuredMessageAttachment `json:"attachment"`
Metadata string `json:"metadata,omitempty"`
} }
// StructuredMessageAttachment is the attachment of a structured message. // StructuredMessageAttachment is the attachment of a structured message.
@ -442,6 +465,39 @@ type StructuredMessagePayload struct {
Buttons *[]StructuredMessageButton `json:"buttons,omitempty"` Buttons *[]StructuredMessageButton `json:"buttons,omitempty"`
Url string `json:"url,omitempty"` Url string `json:"url,omitempty"`
AttachmentID string `json:"attachment_id,omitempty"` AttachmentID string `json:"attachment_id,omitempty"`
ReceiptMessagePayload
}
type ReceiptMessagePayload struct {
RecipientName string `json:"recipient_name,omitempty"`
OrderNumber string `json:"order_number,omitempty"`
Currency string `json:"currency,omitempty"`
PaymentMethod string `json:"payment_method,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
Address *Address `json:"address,omitempty"`
Summary *Summary `json:"summary,omitempty"`
Adjustments []Adjustment `json:"adjustments,omitempty"`
}
type Address struct {
Street1 string `json:"street_1,omitempty"`
Street2 string `json:"street_2,omitempty"`
City string `json:"city,omitempty"`
PostalCode string `json:"postal_code,omitempty"`
State string `json:"state,omitempty"`
Country string `json:"country,omitempty"`
}
type Summary struct {
Subtotal float32 `json:"subtotal,omitempty"`
ShippingCost float32 `json:"shipping_cost,omitempty"`
TotalTax float32 `json:"total_tax,omitempty"`
TotalCost float32 `json:"total_cost,omitempty"`
}
type Adjustment struct {
Name string `json:"name,omitempty"`
Amount float32 `json:"amount,omitempty"`
} }
// StructuredMessageElement is a response containing structural elements // StructuredMessageElement is a response containing structural elements
@ -452,6 +508,13 @@ type StructuredMessageElement struct {
Subtitle string `json:"subtitle"` Subtitle string `json:"subtitle"`
DefaultAction *DefaultAction `json:"default_action,omitempty"` DefaultAction *DefaultAction `json:"default_action,omitempty"`
Buttons []StructuredMessageButton `json:"buttons"` Buttons []StructuredMessageButton `json:"buttons"`
ReceiptMessageElement
}
type ReceiptMessageElement struct {
Quantity float32 `json:"quantity,omitempty"`
Price float32 `json:"price,omitempty"`
Currency string `json:"currency,omitempty"`
} }
// DefaultAction is a response containing default action properties // DefaultAction is a response containing default action properties