Skip to content

Commit

Permalink
First code commit
Browse files Browse the repository at this point in the history
  • Loading branch information
mileusna committed May 29, 2016
1 parent 118aa24 commit d9282cf
Show file tree
Hide file tree
Showing 5 changed files with 404 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_Store
61 changes: 61 additions & 0 deletions facebook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package messenger

import "fmt"

// FacebookRequest received from Facebook server on webhook, contains messages, delivery reports and/or postbacks
type FacebookRequest struct {
Entry []struct {
ID int64 `json:"id,string"`
Messaging []struct {
Recipient struct {
ID int64 `json:"id,string"`
} `json:"recipient"`
Sender struct {
ID int64 `json:"id,string"`
} `json:"sender"`
Timestamp int `json:"timestamp"`
Message *struct {
Mid string `json:"mid"`
Seq int `json:"seq"`
Text string `json:"text"`
} `json:"message,omitempty"`
Delivery *struct {
Mids []string `json:"mids"`
Seq int `json:"seq"`
Watermark int `json:"watermark"`
} `json:"delivery"`
Postback *struct {
Payload string `json:"payload"`
} `json:"postback"`
} `json:"messaging"`
Time int `json:"time"`
} `json:"entry"`
Object string `json:"object"`
}

// rawFBResponse received from Facebook server after sending the message
// if Error is null we copy this into FacebookResponse object
type rawFBResponse struct {
MessageID string `json:"message_id"`
RecipientID int64 `json:"recipient_id,string"`
Error *FacebookError `json:"error"`
}

// FacebookResponse received from Facebook server after sending the message
type FacebookResponse struct {
MessageID string `json:"message_id"`
RecipientID int64 `json:"recipient_id,string"`
}

// FacebookError received form Facebook server if sending messages failed
type FacebookError struct {
Code int `json:"code"`
FbtraceID string `json:"fbtrace_id"`
Message string `json:"message"`
Type string `json:"type"`
}

// Error returns Go error object constructed from FacebookError data
func (err *FacebookError) Error() error {
return fmt.Errorf("FB Error: Type %s: %s; FB trace ID: %s", err.Type, err.Message, err.FbtraceID)
}
167 changes: 167 additions & 0 deletions messages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package messenger

// messenger := &messenger.Messenger {
// VerifyToken: "VERIFY_TOKEN/optional",
// AppSecret: "APP_SECRET/optional",
// AccessToken: "PAGE_ACCESS_TOKEN",
// PageID: "PAGE_ID/optional",
// }

// ButtonType for buttons, it can be ButtonTypeWebURL or ButtonTypePostback
type ButtonType string

type AttachmentType string

type TemplateType string

type NotificationType string

type Message interface {
foo()
}

func (m TextMessage) foo() {}
func (m GenericMessage) foo() {}

const (
// ButtonTypeWebURL is type for web links
ButtonTypeWebURL = ButtonType("web_url")
//ButtonTypePostback is type for postback buttons that sends data back to webhook
ButtonTypePostback = ButtonType("postback")

AttachmentTypeTemplate = AttachmentType("template")

TemplateTypeGeneric = TemplateType("generic")

NotificationTypeRegular = NotificationType("REGULAR")
NotificationTypeSilentPusg = NotificationType("SILENT_PUSH")
NotificationTypeNoPush = NotificationType("NO_PUSH")
)

type TextMessage struct {
Message textMessageContent `json:"message"`
Recipient recipient `json:"recipient"`
NotificationType NotificationType `json:"notification_type,omitempty"`
}

type GenericMessage struct {
Message genericMessageContent `json:"message"`
Recipient recipient `json:"recipient"`
NotificationType NotificationType `json:"notification_type,omitempty"`
}

type recipient struct {
ID int64 `json:"id,string"`
}

type textMessageContent struct {
Text string `json:"text,omitempty"`
}

type genericMessageContent struct {
Attachment *Attachment `json:"attachment,omitempty"`
}

// type MessageData struct {
// Text string `json:"text,omitempty"`
// Attachment *Attachment `json:"attachment,omitempty"`
// }

type Attachment struct {
Type string `json:"type,omitempty"`
Payload payload `json:"payload,omitempty"`
}

type payload struct {
TemplateType string `json:"template_type,omitempty"`
Elements []Element `json:"elements,omitempty"`
}

// Element in Generic Message template attachment
type Element struct {
Title string `json:"title"`
Subtitle string `json:"subtitle,omitempty"`
ItemURL string `json:"item_url,omitempty"`
ImageURL string `json:"image_url,omitempty"`
Buttons []Button `json:"buttons,omitempty"`
}

// Button on Generic Message template element
type Button struct {
Type ButtonType `json:"type"`
URL string `json:"url,omitempty"`
Title string `json:"title"`
Payload string `json:"payload,omitempty"`
}

// NewTextMessage creates new text message for receiverID
// This function is here for convenient reason, you will
// probably use shorthand version SentTextMessage which sends message immediatly
func NewTextMessage(receiverID int64, text string) TextMessage {
return TextMessage{
Recipient: recipient{ID: receiverID},
Message: textMessageContent{Text: text},
}
}

// NewGenericMessage creates new Generic Template message for receiverID
// Generic template messages are used for structured messages with images, links, buttons and postbacks
func NewGenericMessage(receiverID int64) GenericMessage {
return GenericMessage{
Recipient: recipient{ID: receiverID},
Message: genericMessageContent{
Attachment: &Attachment{
Type: "template",
Payload: payload{TemplateType: "generic"},
},
},
}
}

// AddNewElement adds element to Generic template message with defined title, subtitle, link url and image url
// Only title is mandatory, other params can be empty string
// Generic messages can have up to 10 elements which are scolled horizontaly in users messenger
func (m *GenericMessage) AddNewElement(title, subtitle, itemURL, imageURL string) {
m.AddElement(NewElement(title, subtitle, itemURL, imageURL))
}

// AddElement adds element e to Generic Message
// Generic messages can have up to 10 elements which are scolled horizontaly in users messenger
// If element contain buttons, you can create element with NewElement, than add some buttons with
// AddWebURLButton and AddPostbackButton and add it to message using this method
func (m *GenericMessage) AddElement(e Element) {
m.Message.Attachment.Payload.Elements = append(m.Message.Attachment.Payload.Elements, e)
}

// NewElement creates new element with defined title, subtitle, link url and image url
// Only title is mandatory, other params can be empty string
// Instead of calling this function you can also initialize Element struct, depends what you prefere
func NewElement(title, subtitle, itemURL, imageURL string) Element {
e := Element{
Title: title,
Subtitle: subtitle,
ItemURL: itemURL,
ImageURL: imageURL,
}
return e
}

// AddWebURLButton adds web link URL button to the element
func (e *Element) AddWebURLButton(title, URL string) {
b := Button{
Type: ButtonTypeWebURL,
Title: title,
URL: URL,
}
e.Buttons = append(e.Buttons, b)
}

// AddPostbackButton adds button that sends payload string back to webhook when pressed
func (e *Element) AddPostbackButton(title, payload string) {
b := Button{
Type: ButtonTypePostback,
Title: title,
Payload: payload,
}
e.Buttons = append(e.Buttons, b)
}
92 changes: 92 additions & 0 deletions messenger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package messenger

import (
"bytes"
"encoding/json"
"log"
"net/http"
)

const apiURL = "https://graph.facebook.com/v2.6/"

// TestURL to mock FB server, used for testing
var TestURL = ""

// Messenger struct
type Messenger struct {
AccessToken string
PageID string
apiURL string
pageURL string
}

// New creates new messenger instance
func New(accessToken, pageID string) Messenger {
return Messenger{
AccessToken: accessToken,
PageID: pageID,
}
}

// SendMessage sends chat message
func (msng *Messenger) SendMessage(m Message) (FacebookResponse, error) {
if msng.apiURL == "" {
if TestURL != "" {
msng.apiURL = TestURL + "me/messages?access_token=" + msng.AccessToken // testing, mock FB URL
} else {
msng.apiURL = apiURL + "me/messages?access_token=" + msng.AccessToken
}
}

s, _ := json.Marshal(m)
log.Println("MESSAGE:", string(s))
req, err := http.NewRequest("POST", msng.apiURL, bytes.NewBuffer(s))
req.Header.Set("Content-Type", "application/json")

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return FacebookResponse{}, err
}

return decodeResponse(resp)
}

// SendTextMessage sends text messate to receiverID
// it is shorthand instead of crating new text message and then sending it
func (msng Messenger) SendTextMessage(receiverID int64, text string) (FacebookResponse, error) {
m := NewTextMessage(receiverID, text)
return msng.SendMessage(&m)
}

// DecodeRequest decodes http request from FB messagner to FacebookRequest struct
// DecodeRequest will close the Body reader
func DecodeRequest(r *http.Request) FacebookRequest {
defer r.Body.Close()
var fbRq FacebookRequest
err := json.NewDecoder(r.Body).Decode(&fbRq)
if err != nil {
log.Println(err)
}

return fbRq
}

// decodeResponse decodes Facebook response after sending message, usually contains MessageID or Error
func decodeResponse(r *http.Response) (FacebookResponse, error) {
defer r.Body.Close()
var fbResp rawFBResponse
err := json.NewDecoder(r.Body).Decode(&fbResp)
if err != nil {
return FacebookResponse{}, nil
}

if fbResp.Error != nil {
return FacebookResponse{}, fbResp.Error.Error()
}

return FacebookResponse{
MessageID: fbResp.MessageID,
RecipientID: fbResp.RecipientID,
}, nil
}
Loading

0 comments on commit d9282cf

Please sign in to comment.