├── .travis.yml
├── pass_thread_control.go
├── go.mod
├── profile.go
├── .gitignore
├── go.sum
├── actions.go
├── LICENSE
├── settings.go
├── examples
├── extension
│ └── main.go
├── basic
│ └── main.go
└── linked-account
│ └── main.go
├── README.md
├── message.go
├── receiving.go
├── messenger_test.go
├── response.go
└── messenger.go
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | go:
3 | - 1.10.x
4 | - master
5 |
6 | go_import_path: github.com/paked/messenger
7 |
8 | install: go get -t ./...
9 | script:
10 | - go test -v ./...
11 | - go build ./examples/...
12 |
--------------------------------------------------------------------------------
/pass_thread_control.go:
--------------------------------------------------------------------------------
1 | package messenger
2 |
3 | type passThreadControl struct {
4 | Recipient Recipient `json:"recipient"`
5 | TargetAppID int64 `json:"target_app_id"`
6 | Metadata string `json:"metadata"`
7 | }
8 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/paked/messenger
2 |
3 | require (
4 | github.com/davecgh/go-spew v1.1.1 // indirect
5 | github.com/pmezard/go-difflib v1.0.0 // indirect
6 | github.com/stretchr/testify v1.2.2
7 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
8 | )
9 |
--------------------------------------------------------------------------------
/profile.go:
--------------------------------------------------------------------------------
1 | package messenger
2 |
3 | // Profile is the public information of a Facebook user
4 | type Profile struct {
5 | Name string `json:"name"`
6 | FirstName string `json:"first_name"`
7 | LastName string `json:"last_name"`
8 | ProfilePicURL string `json:"profile_pic"`
9 | Locale string `json:"locale"`
10 | Timezone float64 `json:"timezone"`
11 | Gender string `json:"gender"`
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 |
26 | # Configuration
27 | cmd/bot/config.json
28 |
29 | .idea
30 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
6 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
7 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
8 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
9 |
--------------------------------------------------------------------------------
/actions.go:
--------------------------------------------------------------------------------
1 | package messenger
2 |
3 | // Action is used to determine what kind of message a webhook event is.
4 | type Action int
5 |
6 | const (
7 | // UnknownAction means that the event was not able to be classified.
8 | UnknownAction Action = iota - 1
9 | // TextAction means that the event was a text message (May contain attachments).
10 | TextAction
11 | // DeliveryAction means that the event was advising of a successful delivery to a
12 | // previous recipient.
13 | DeliveryAction
14 | // ReadAction means that the event was a previous recipient reading their respective
15 | // messages.
16 | ReadAction
17 | // PostBackAction represents post call back
18 | PostBackAction
19 | // OptInAction represents opting in through the Send to Messenger button
20 | OptInAction
21 | // ReferralAction represents ?ref parameter in m.me URLs
22 | ReferralAction
23 | // AccountLinkingAction means that the event concerns changes in account linking
24 | // status.
25 | AccountLinkingAction
26 | )
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Harrison Shoebridge
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/settings.go:
--------------------------------------------------------------------------------
1 | package messenger
2 |
3 | // Defines the different sizes available when setting up a CallToActionsItem
4 | // of type "web_url". These values can be used in the "WebviewHeightRatio"
5 | // field.
6 | const (
7 | // WebviewCompact opens the page in a web view that takes half the screen
8 | // and covers only part of the conversation.
9 | WebviewCompact = "compact"
10 |
11 | // WebviewTall opens the page in a web view that covers about 75% of the
12 | // conversation.
13 | WebviewTall = "tall"
14 |
15 | // WebviewFull opens the page in a web view that completely covers the
16 | // conversation, and has a "back" button instead of a "close" one.
17 | WebviewFull = "full"
18 | )
19 |
20 | // GreetingSetting is the setting for greeting message
21 | type GreetingSetting struct {
22 | SettingType string `json:"setting_type"`
23 | Greeting GreetingInfo `json:"greeting"`
24 | }
25 |
26 | // GreetingInfo contains greeting message
27 | type GreetingInfo struct {
28 | Text string `json:"text"`
29 | }
30 |
31 | // CallToActionsSetting is the settings for Get Started and Persist Menu
32 | type CallToActionsSetting struct {
33 | SettingType string `json:"setting_type"`
34 | ThreadState string `json:"thread_state"`
35 | CallToActions []CallToActionsItem `json:"call_to_actions"`
36 | }
37 |
38 | // CallToActionsItem contains Get Started button or item of Persist Menu
39 | type CallToActionsItem struct {
40 | Type string `json:"type,omitempty"`
41 | Title string `json:"title,omitempty"`
42 | Payload string `json:"payload,omitempty"`
43 | URL string `json:"url,omitempty"`
44 | WebviewHeightRatio string `json:"webview_height_ratio,omitempty"`
45 | MessengerExtension bool `json:"messenger_extensions,omitempty"`
46 | }
47 |
48 | // HomeURL is the settings for EnableChatExtension
49 | // https://developers.facebook.com/docs/messenger-platform/reference/messenger-profile-api/home-url
50 | type HomeURL struct {
51 | URL string `json:"url,omitempty"`
52 | WebviewHeightRatio string `json:"webview_height_ratio,omitempty"`
53 | WebviewShareButton string `json:"webview_share_button,omitempty"`
54 | InTest bool `json:"in_test,omitempty"`
55 | }
56 |
--------------------------------------------------------------------------------
/examples/extension/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "net/http"
8 | "os"
9 | "time"
10 |
11 | "github.com/paked/messenger"
12 | )
13 |
14 | var (
15 | serverURL = flag.String("serverURL", "", "The server (webview) URL, must be https (required)")
16 | verifyToken = flag.String("verify-token", "mad-skrilla", "The token used to verify facebook (required)")
17 | verify = flag.Bool("should-verify", false, "Whether or not the app should verify itself")
18 | pageToken = flag.String("page-token", "not skrilla", "The token that is used to verify the page on facebook")
19 | appSecret = flag.String("app-secret", "", "The app secret from the facebook developer portal (required)")
20 | host = flag.String("host", "localhost", "The host used to serve the messenger bot")
21 | port = flag.Int("port", 8080, "The port used to serve the messenger bot")
22 | )
23 |
24 | func main() {
25 | flag.Parse()
26 |
27 | if *verifyToken == "" || *appSecret == "" || *pageToken == "" {
28 | fmt.Printf("missing arguments\n\n")
29 | flag.Usage()
30 |
31 | os.Exit(-1)
32 | }
33 |
34 | client := messenger.New(messenger.Options{
35 | Verify: *verify,
36 | AppSecret: *appSecret,
37 | VerifyToken: *verifyToken,
38 | Token: *pageToken,
39 | })
40 |
41 | err := client.EnableChatExtension(messenger.HomeURL{
42 | URL: *serverURL,
43 | WebviewHeightRatio: "tall",
44 | WebviewShareButton: "show",
45 | InTest: true,
46 | })
47 | if err != nil {
48 | fmt.Println("Failed to EnableChatExtension, err=", err)
49 | }
50 |
51 | // Setup a handler to be triggered when a message is received
52 | client.HandleMessage(func(m messenger.Message, r *messenger.Response) {
53 | fmt.Printf("%v (Sent, %v)\n", m.Text, m.Time.Format(time.UnixDate))
54 |
55 | p, err := client.ProfileByID(m.Sender.ID, []string{"name", "first_name", "last_name", "profile_pic"})
56 | if err != nil {
57 | fmt.Println("Something went wrong!", err)
58 | }
59 |
60 | r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), messenger.ResponseType)
61 | })
62 |
63 | addr := fmt.Sprintf("%s:%d", *host, *port)
64 | log.Println("Serving messenger bot on", addr)
65 | log.Fatal(http.ListenAndServe(addr, client.Handler()))
66 | }
67 |
--------------------------------------------------------------------------------
/examples/basic/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "net/http"
8 | "os"
9 | "time"
10 |
11 | "github.com/paked/messenger"
12 | )
13 |
14 | var (
15 | verifyToken = flag.String("verify-token", "mad-skrilla", "The token used to verify facebook (required)")
16 | verify = flag.Bool("should-verify", false, "Whether or not the app should verify itself")
17 | pageToken = flag.String("page-token", "not skrilla", "The token that is used to verify the page on facebook")
18 | appSecret = flag.String("app-secret", "", "The app secret from the facebook developer portal (required)")
19 | host = flag.String("host", "localhost", "The host used to serve the messenger bot")
20 | port = flag.Int("port", 8080, "The port used to serve the messenger bot")
21 | )
22 |
23 | func main() {
24 | flag.Parse()
25 |
26 | if *verifyToken == "" || *appSecret == "" || *pageToken == "" {
27 | fmt.Println("missing arguments")
28 | fmt.Println()
29 | flag.Usage()
30 |
31 | os.Exit(-1)
32 | }
33 |
34 | // Create a new messenger client
35 | client := messenger.New(messenger.Options{
36 | Verify: *verify,
37 | AppSecret: *appSecret,
38 | VerifyToken: *verifyToken,
39 | Token: *pageToken,
40 | })
41 |
42 | // Setup a handler to be triggered when a message is received
43 | client.HandleMessage(func(m messenger.Message, r *messenger.Response) {
44 | fmt.Printf("%v (Sent, %v)\n", m.Text, m.Time.Format(time.UnixDate))
45 |
46 | p, err := client.ProfileByID(m.Sender.ID, []string{"name", "first_name", "last_name", "profile_pic"})
47 | if err != nil {
48 | fmt.Println("Something went wrong!", err)
49 | }
50 |
51 | r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), messenger.ResponseType)
52 | })
53 |
54 | // Setup a handler to be triggered when a message is delivered
55 | client.HandleDelivery(func(d messenger.Delivery, r *messenger.Response) {
56 | fmt.Println("Delivered at:", d.Watermark().Format(time.UnixDate))
57 | })
58 |
59 | // Setup a handler to be triggered when a message is read
60 | client.HandleRead(func(m messenger.Read, r *messenger.Response) {
61 | fmt.Println("Read at:", m.Watermark().Format(time.UnixDate))
62 | })
63 |
64 | addr := fmt.Sprintf("%s:%d", *host, *port)
65 | log.Println("Serving messenger bot on", addr)
66 | log.Fatal(http.ListenAndServe(addr, client.Handler()))
67 | }
68 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **[See github.com/sSimuSs/messenger for an up-to-date fork of this repo.](https://github.com/sSimuSs/messenger)**
2 |
3 | # Messenger [](https://godoc.org/github.com/paked/messenger) [](https://travis-ci.org/paked/messenger)
4 |
5 | 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.
6 |
7 | You can find [examples for this library here](https://github.com/paked/messenger/blob/master/examples/).
8 |
9 | We tag our releases Semver style.
10 |
11 | ## Tips
12 |
13 | - Follow the [quickstart](https://developers.facebook.com/docs/messenger-platform/quickstart) guide for getting everything set up!
14 | - You need a Facebook development app, and a Facebook page in order to build things.
15 | - Use [ngrok](https://ngrok.com) to tunnel your locally running bot so that Facebook can reach the webhook.
16 |
17 | ## Breaking Changes
18 |
19 | 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.
20 |
21 | `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.
22 |
23 | - 06/2/18: Added messaging_type field for message send API request as it is required by FB
24 | - [23/1/17](https://github.com/paked/messenger/commit/1145fe35249f8ce14d3c0a52544e4a4babdc15a4): Updating timezone type to `float64` in profile struct
25 | - [12/9/16](https://github.com/paked/messenger/commit/47f193fc858e2d710c061e88b12dbd804a399e57): Removing unused parameter `text string` from function `(r *Response) GenericTemplate`.
26 | - [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.
27 | - [4/5/16](https://github.com/paked/messenger/commit/eb0e72a5dcd3bfaffcfe88dced6d6ac5247f9da1): The URL to use for the webhook is changable in the `Options` struct.
28 |
29 | ## Inspiration
30 |
31 | Messenger takes design cues from:
32 |
33 | - [`net/http`](https://godoc.org/net/http)
34 | - [`github.com/nickvanw/ircx`](https://github.com/nickvanw/ircx)
35 |
36 | ## Projects
37 |
38 | 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.
39 |
40 | - [meme-maker](https://github.com/paked/meme-maker) by @paked: A bot which, given a photo and a caption, will create a macro meme.
41 | - [drone-facebook](https://github.com/appleboy/drone-facebook) by @appleboy: [Drone.io](https://drone.io) plugin which sends Facebook notifications
42 |
--------------------------------------------------------------------------------
/message.go:
--------------------------------------------------------------------------------
1 | package messenger
2 |
3 | import (
4 | "encoding/json"
5 | "time"
6 | )
7 |
8 | // Message represents a Facebook messenger message.
9 | type Message struct {
10 | // Sender is who the message was sent from.
11 | Sender Sender `json:"-"`
12 | // Recipient is who the message was sent to.
13 | Recipient Recipient `json:"-"`
14 | // Time is when the message was sent.
15 | Time time.Time `json:"-"`
16 | // Message is mine
17 | IsEcho bool `json:"is_echo,omitempty"`
18 | // Mid is the ID of the message.
19 | Mid string `json:"mid"`
20 | // Seq is order the message was sent in relation to other messages.
21 | Seq int `json:"seq"`
22 | // StickerID is the ID of the sticker user sent.
23 | StickerID int `json:"sticker_id"`
24 | // Text is the textual contents of the message.
25 | Text string `json:"text"`
26 | // Attachments is the information about the attachments which were sent
27 | // with the message.
28 | Attachments []Attachment `json:"attachments"`
29 | // Selected quick reply
30 | QuickReply *QuickReply `json:"quick_reply,omitempty"`
31 | // Entities for NLP
32 | // https://developers.facebook.com/docs/messenger-platform/built-in-nlp/
33 | NLP json.RawMessage `json:"nlp"`
34 | }
35 |
36 | // Delivery represents a the event fired when Facebook delivers a message to the
37 | // recipient.
38 | type Delivery struct {
39 | // Mids are the IDs of the messages which were read.
40 | Mids []string `json:"mids"`
41 | // RawWatermark is the timestamp of when the delivery was.
42 | RawWatermark int64 `json:"watermark"`
43 | // Seq is the sequence the message was sent in.
44 | Seq int `json:"seq"`
45 | }
46 |
47 | // Read represents a the event fired when a message is read by the
48 | // recipient.
49 | type Read struct {
50 | // RawWatermark is the timestamp before which all messages have been read
51 | // by the user
52 | RawWatermark int64 `json:"watermark"`
53 | // Seq is the sequence the message was sent in.
54 | Seq int `json:"seq"`
55 | }
56 |
57 | // PostBack represents postback callback
58 | type PostBack struct {
59 | // Sender is who the message was sent from.
60 | Sender Sender `json:"-"`
61 | // Recipient is who the message was sent to.
62 | Recipient Recipient `json:"-"`
63 | // Time is when the message was sent.
64 | Time time.Time `json:"-"`
65 | // PostBack ID
66 | Payload string `json:"payload"`
67 | // Optional referral info
68 | Referral Referral `json:"referral"`
69 | }
70 |
71 | type AccountLinking struct {
72 | // Sender is who the message was sent from.
73 | Sender Sender `json:"-"`
74 | // Recipient is who the message was sent to.
75 | Recipient Recipient `json:"-"`
76 | // Time is when the message was sent.
77 | Time time.Time `json:"-"`
78 | // Status represents the new account linking status.
79 | Status string `json:"status"`
80 | // AuthorizationCode is a pass-through code set during the linking process.
81 | AuthorizationCode string `json:"authorization_code"`
82 | }
83 |
84 | // Watermark is the RawWatermark timestamp rendered as a time.Time.
85 | func (d Delivery) Watermark() time.Time {
86 | return time.Unix(d.RawWatermark/int64(time.Microsecond), 0)
87 | }
88 |
89 | // Watermark is the RawWatermark timestamp rendered as a time.Time.
90 | func (r Read) Watermark() time.Time {
91 | return time.Unix(r.RawWatermark/int64(time.Microsecond), 0)
92 | }
93 |
94 | // GetNLP simply unmarshals the NLP entities to the given struct and returns
95 | // an error if it's not possible
96 | func (m *Message) GetNLP(i interface{}) error {
97 | return json.Unmarshal(m.NLP, &i)
98 | }
99 |
--------------------------------------------------------------------------------
/receiving.go:
--------------------------------------------------------------------------------
1 | package messenger
2 |
3 | import "time"
4 |
5 | // Receive is the format in which webhook events are sent.
6 | type Receive struct {
7 | // Object should always be `page`. (I don't quite understand why)
8 | Object string `json:"object"`
9 | // Entry is all of the different messenger types which were
10 | // sent in this event.
11 | Entry []Entry `json:"entry"`
12 | }
13 |
14 | // Entry is a batch of events which were sent in this webhook trigger.
15 | type Entry struct {
16 | // ID is the ID of the batch.
17 | ID int64 `json:"id,string"`
18 | // Time is when the batch was sent.
19 | Time int64 `json:"time"`
20 | // Messaging is the events that were sent in this Entry
21 | Messaging []MessageInfo `json:"messaging"`
22 | }
23 |
24 | // MessageInfo is an event that is fired by the webhook.
25 | type MessageInfo struct {
26 | // Sender is who the event was sent from.
27 | Sender Sender `json:"sender"`
28 | // Recipient is who the event was sent to.
29 | Recipient Recipient `json:"recipient"`
30 | // Timestamp is the true time the event was triggered.
31 | Timestamp int64 `json:"timestamp"`
32 | // Message is the contents of a message if it is a MessageAction.
33 | // Nil if it is not a MessageAction.
34 | Message *Message `json:"message"`
35 | // Delivery is the contents of a message if it is a DeliveryAction.
36 | // Nil if it is not a DeliveryAction.
37 | Delivery *Delivery `json:"delivery"`
38 |
39 | PostBack *PostBack `json:"postback"`
40 |
41 | Read *Read `json:"read"`
42 |
43 | OptIn *OptIn `json:"optin"`
44 |
45 | ReferralMessage *ReferralMessage `json:"referral"`
46 |
47 | AccountLinking *AccountLinking `json:"account_linking"`
48 | }
49 |
50 | type OptIn struct {
51 | // Sender is the sender of the message
52 | Sender Sender `json:"-"`
53 | // Recipient is who the message was sent to.
54 | Recipient Recipient `json:"-"`
55 | // Time is when the message was sent.
56 | Time time.Time `json:"-"`
57 | // Ref is the reference as given
58 | Ref string `json:"ref"`
59 | }
60 |
61 | // ReferralMessage represents referral endpoint
62 | type ReferralMessage struct {
63 | *Referral
64 |
65 | // Sender is the sender of the message
66 | Sender Sender `json:"-"`
67 | // Recipient is who the message was sent to.
68 | Recipient Recipient `json:"-"`
69 | // Time is when the message was sent.
70 | Time time.Time `json:"-"`
71 | }
72 |
73 | // Referral represents referral info
74 | type Referral struct {
75 | // Data originally passed in the ref param
76 | Ref string `json:"ref"`
77 | // Source type
78 | Source string `json:"source"`
79 | // The identifier dor the referral
80 | Type string `json:"type"`
81 | }
82 |
83 | // Sender is who the message was sent from.
84 | type Sender struct {
85 | ID int64 `json:"id,string"`
86 | }
87 |
88 | // Recipient is who the message was sent to.
89 | type Recipient struct {
90 | ID int64 `json:"id,string"`
91 | }
92 |
93 | // Attachment is a file which used in a message.
94 | type Attachment struct {
95 | Title string `json:"title,omitempty"`
96 | URL string `json:"url,omitempty"`
97 | // Type is what type the message is. (image, video, audio or location)
98 | Type string `json:"type"`
99 | // Payload is the information for the file which was sent in the attachment.
100 | Payload Payload `json:"payload"`
101 | }
102 |
103 | // QuickReply is a file which used in a message.
104 | type QuickReply struct {
105 | // ContentType is the type of reply
106 | ContentType string `json:"content_type,omitempty"`
107 | // Title is the reply title
108 | Title string `json:"title,omitempty"`
109 | // Payload is the reply information
110 | Payload string `json:"payload"`
111 | }
112 |
113 | // Payload is the information on where an attachment is.
114 | type Payload struct {
115 | // URL is where the attachment resides on the internet.
116 | URL string `json:"url,omitempty"`
117 | // Coordinates is Lat/Long pair of location pin
118 | Coordinates *Coordinates `json:"coordinates,omitempty"`
119 | }
120 |
121 | // Coordinates is a pair of latitude and longitude
122 | type Coordinates struct {
123 | // Lat is latitude
124 | Lat float64 `json:"lat"`
125 | // Long is longitude
126 | Long float64 `json:"long"`
127 | }
128 |
--------------------------------------------------------------------------------
/examples/linked-account/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "net/http"
8 | "net/url"
9 | "os"
10 | "path"
11 | "strings"
12 | "time"
13 |
14 | "github.com/paked/messenger"
15 | )
16 |
17 | const (
18 | webhooksPath = "/webhooks"
19 | loginPath = "/signin"
20 |
21 | validUsername = "john"
22 | validPassword = "secret"
23 | )
24 |
25 | var (
26 | verifyToken = flag.String("verify-token", "", "The token used to verify facebook (required)")
27 | pageToken = flag.String("page-token", "", "The token that is used to verify the page on facebook.")
28 | appSecret = flag.String("app-secret", "", "The app secret from the facebook developer portal (required)")
29 | host = flag.String("host", "localhost", "The host used to serve the messenger bot")
30 | port = flag.Int("port", 8080, "The port used to serve the messenger bot")
31 | publicHost = flag.String("public-host", "example.org", "The public facing host used to access the messenger bot")
32 | )
33 |
34 | func main() {
35 | flag.Parse()
36 |
37 | if *verifyToken == "" || *appSecret == "" || *pageToken == "" {
38 | fmt.Println("missing arguments")
39 | fmt.Println()
40 | flag.Usage()
41 |
42 | os.Exit(-1)
43 | }
44 |
45 | // Instantiate messenger client
46 | client := messenger.New(messenger.Options{
47 | AppSecret: *appSecret,
48 | VerifyToken: *verifyToken,
49 | Token: *pageToken,
50 | })
51 |
52 | // Handle incoming messages
53 | client.HandleMessage(func(m messenger.Message, r *messenger.Response) {
54 | log.Printf("%v (Sent, %v)\n", m.Text, m.Time.Format(time.UnixDate))
55 |
56 | p, err := client.ProfileByID(m.Sender.ID, []string{"name", "first_name", "last_name", "profile_pic"})
57 | if err != nil {
58 | log.Println("Failed to fetch user profile:", err)
59 | }
60 |
61 | switch strings.ToLower(m.Text) {
62 | case "login":
63 | err = loginButton(r)
64 | case "logout":
65 | err = logoutButton(r)
66 | case "help":
67 | err = help(p, r)
68 | default:
69 | err = greeting(p, r)
70 | }
71 |
72 | if err != nil {
73 | log.Println("Failed to respond:", err)
74 | }
75 | })
76 |
77 | // Send a feedback to the user after an update of account linking status
78 | client.HandleAccountLinking(func(m messenger.AccountLinking, r *messenger.Response) {
79 | var text string
80 | switch m.Status {
81 | case "linked":
82 | text = "Hey there! You're now logged in :)"
83 | case "unlinked":
84 | text = "You've been logged out of your account."
85 | }
86 |
87 | if err := r.Text(text, messenger.ResponseType); err != nil {
88 | log.Println("Failed to send account linking feedback")
89 | }
90 | })
91 |
92 | // Setup router
93 | mux := http.NewServeMux()
94 | mux.Handle(webhooksPath, client.Handler())
95 | mux.HandleFunc(loginPath, func(w http.ResponseWriter, r *http.Request) {
96 | switch r.Method {
97 | case "GET":
98 | loginForm(w, r)
99 | case "POST":
100 | login(w, r)
101 | }
102 | })
103 |
104 | // Listen
105 | addr := fmt.Sprintf("%s:%d", *host, *port)
106 | log.Println("Serving messenger bot on", addr)
107 | log.Fatal(http.ListenAndServe(addr, mux))
108 | }
109 |
110 | // loginButton will present to the user a button that can be used to
111 | // start the account linking process.
112 | func loginButton(r *messenger.Response) error {
113 | buttons := &[]messenger.StructuredMessageButton{
114 | {
115 | Type: "account_link",
116 | URL: "https://" + path.Join(*publicHost, loginPath),
117 | },
118 | }
119 | return r.ButtonTemplate("Link your account.", buttons, messenger.ResponseType)
120 | }
121 |
122 | // logoutButton show to the user a button that can be used to start
123 | // the process of unlinking an account.
124 | func logoutButton(r *messenger.Response) error {
125 | buttons := &[]messenger.StructuredMessageButton{
126 | {
127 | Type: "account_unlink",
128 | },
129 | }
130 | return r.ButtonTemplate("Unlink your account.", buttons, messenger.ResponseType)
131 | }
132 |
133 | // greeting salutes the user.
134 | func greeting(p messenger.Profile, r *messenger.Response) error {
135 | return r.Text(fmt.Sprintf("Hello, %v!", p.FirstName), messenger.ResponseType)
136 | }
137 |
138 | // help displays possibles actions to the user.
139 | func help(p messenger.Profile, r *messenger.Response) error {
140 | text := fmt.Sprintf(
141 | "%s, looking for actions to do? Here is what I understand.",
142 | p.FirstName,
143 | )
144 |
145 | replies := []messenger.QuickReply{
146 | {
147 | ContentType: "text",
148 | Title: "Login",
149 | },
150 | {
151 | ContentType: "text",
152 | Title: "Logout",
153 | },
154 | }
155 |
156 | return r.TextWithReplies(text, replies, messenger.ResponseType)
157 | }
158 |
159 | // loginForm is the endpoint responsible to displays a login
160 | // form. During the account linking process, after clicking on the
161 | // login button, users are directed to this form where they are
162 | // supposed to sign into their account. When the form is submitted,
163 | // credentials are sent to the login endpoint.
164 | func loginForm(w http.ResponseWriter, r *http.Request) {
165 | values := r.URL.Query()
166 | linkingToken := values.Get("account_linking_token")
167 | redirectURI := values.Get("redirect_uri")
168 | fmt.Fprint(w, templateLogin(loginPath, linkingToken, redirectURI, false))
169 | }
170 |
171 | // login is the endpoint that handles the actual signing in, by
172 | // checking the credentials, then redirecting to Facebook Messenger if
173 | // they are valid.
174 | func login(w http.ResponseWriter, r *http.Request) {
175 | r.ParseForm()
176 |
177 | username := r.FormValue("username")
178 | password := r.FormValue("password")
179 | linkingToken := r.FormValue("account_linking_token")
180 | rawRedirect := r.FormValue("redirect_uri")
181 |
182 | if !checkCredentials(username, password) {
183 | fmt.Fprint(w, templateLogin(loginPath, linkingToken, rawRedirect, true))
184 | return
185 | }
186 |
187 | redirectURL, err := url.Parse(rawRedirect)
188 | if err != nil {
189 | log.Println("failed to parse url:", err)
190 | return
191 | }
192 |
193 | q := redirectURL.Query()
194 | q.Set("authorization_code", "something")
195 | redirectURL.RawQuery = q.Encode()
196 |
197 | w.Header().Set("Location", redirectURL.String())
198 | w.WriteHeader(http.StatusFound)
199 | }
200 |
201 | func checkCredentials(username, password string) bool {
202 | return username == validUsername && password == validPassword
203 | }
204 |
205 | // templateLogin constructs the signin form.
206 | func templateLogin(loginPath, linkingToken, redirectURI string, failed bool) string {
207 | failedInfo := ""
208 | if failed {
209 | failedInfo = `