1# A Facebook Graph API SDK In Golang # 2 3[![Build Status](https://travis-ci.org/huandu/facebook.svg?branch=master)](https://travis-ci.org/huandu/facebook) 4[![GoDoc](https://godoc.org/github.com/huandu/facebook?status.svg)](https://godoc.org/github.com/huandu/facebook) 5 6This is a Go package that fully supports the [Facebook Graph API](https://developers.facebook.com/docs/graph-api/) with file upload, batch request and marketing API. It can be used in Google App Engine. 7 8API documentation can be found on [godoc](http://godoc.org/github.com/huandu/facebook). 9 10Feel free to create an issue or send me a pull request if you have any "how-to" question or bug or suggestion when using this package. I'll try my best to reply it. 11 12## Get It ## 13 14Use `go get -u github.com/huandu/facebook` to get or update it. 15 16## Usage ## 17 18### Quick start ### 19 20Here is a sample that reads my Facebook first name by uid. 21 22```go 23package main 24 25import ( 26 "fmt" 27 fb "github.com/huandu/facebook" 28) 29 30func main() { 31 res, _ := fb.Get("/538744468", fb.Params{ 32 "fields": "first_name", 33 "access_token": "a-valid-access-token", 34 }) 35 fmt.Println("Here is my Facebook first name:", res["first_name"]) 36} 37``` 38 39The type of `res` is `fb.Result` (a.k.a. `map[string]interface{}`). 40This type has several useful methods to decode `res` to any Go type safely. 41 42```go 43// Decode "first_name" to a Go string. 44var first_name string 45res.DecodeField("first_name", &first_name) 46fmt.Println("Here's an alternative way to get first_name:", first_name) 47 48// It's also possible to decode the whole result into a predefined struct. 49type User struct { 50 FirstName string 51} 52 53var user User 54res.Decode(&user) 55fmt.Println("print first_name in struct:", user.FirstName) 56``` 57 58If a type implements the `json.Unmarshaler` interface, `Decode` or `DecodeField` will use it to unmarshal JSON. 59 60```go 61res := Result{ 62 "create_time": "2006-01-02T15:16:17Z", 63} 64 65// Type `*time.Time` implements `json.Unmarshaler`. 66// res.DecodeField will use the interface to unmarshal data. 67var tm time.Time 68res.DecodeField("create_time", &tm) 69``` 70 71### Read a graph `user` object with a valid access token ### 72 73```go 74res, err := fb.Get("/me/feed", fb.Params{ 75 "access_token": "a-valid-access-token", 76}) 77 78if err != nil { 79 // err can be a Facebook API error. 80 // if so, the Error struct contains error details. 81 if e, ok := err.(*Error); ok { 82 fmt.Printf("facebook error. [message:%v] [type:%v] [code:%v] [subcode:%v] [trace:%v]", 83 e.Message, e.Type, e.Code, e.ErrorSubcode, e.TraceID) 84 return 85 } 86 87 return 88} 89 90// read my last feed story. 91fmt.Println("My latest feed story is:", res.Get("data.0.story")) 92``` 93 94### Read a graph `search` for page and decode slice of maps 95 96```go 97res, _ := fb.Get("/search", fb.Params{ 98 "access_token": "a-valid-access-token", 99 "type": "page", 100 "q": "nightlife,singapore", 101 }) 102 103var items []fb.Result 104 105err := res.DecodeField("data", &items) 106 107if err != nil { 108 fmt.Printf("An error has happened %v", err) 109 return 110} 111 112for _, item := range items { 113 fmt.Println(item["id"]) 114} 115``` 116 117### Use `App` and `Session` ### 118 119It's recommended to use `App` and `Session` in a production app. They provide more control over all API calls. They can also make code clearer and more concise. 120 121```go 122// Create a global App var to hold app id and secret. 123var globalApp = fb.New("your-app-id", "your-app-secret") 124 125// Facebook asks for a valid redirect uri when parsing signed request. 126// It's a new enforced policy starting as of late 2013. 127globalApp.RedirectUri = "http://your.site/canvas/url/" 128 129// Here comes a client with a Facebook signed request string in query string. 130// This will return a new session from a signed request. 131session, _ := globalApp.SessionFromSignedRequest(signedRequest) 132 133// If there is another way to get decoded access token, 134// this will return a session created directly from the token. 135session := globalApp.Session(token) 136 137// This validates the access token by ensuring that the current user ID is properly returned. err is nil if token is valid. 138err := session.Validate() 139 140// Use the new session to send an API request with access token. 141res, _ := session.Get("/me/feed", nil) 142``` 143 144By default all requests are sent to Facebook servers. If you wish to override API base URL for unit-testing purposes - just set respective `Session` field: 145```go 146testSrv := httptest.NewServer(someMux) 147session.BaseURL = testSrv.URL + "/" 148``` 149 150Facebook returns most timestamps in a ISO9601 format which can't be natively parsed by Go's `encoding/json`. 151Setting `RFC3339Timestamps` `true` on the `Session` or at the global level will cause proper RFC3339 timestamps to be requested from Facebook. 152RFC3339 is what `encoding/json` natively expects. 153 154```go 155fb.RFC3339Timestamps = true 156session.RFC3339Timestamps = true 157``` 158 159Setting either of these to true will cause `date_format=Y-m-d\TH:i:sP` to be sent as a parameter on every request. The format string is a PHP `date()` representation of RFC3339. 160More info is available in [this issue](https://github.com/huandu/facebook/issues/95). 161 162### Use `paging` field in response. ### 163 164Some Graph API responses use a special JSON structure to provide paging information. Use `Result.Paging()` to walk through all data in such results. 165 166```go 167res, _ := session.Get("/me/home", nil) 168 169// create a paging structure. 170paging, _ := res.Paging(session) 171 172var allResults []Result 173 174// append first page of results to slice of Result 175allResults = append(allResults, paging.Data()...) 176 177for { 178 // get next page. 179 noMore, err := paging.Next() 180 if err != nil { 181 panic(err) 182 } 183 if noMore { 184 // No more results available 185 break 186 } 187 // append current page of results to slice of Result 188 allResults = append(allResults, paging.Data()...) 189} 190 191``` 192 193### Read Graph API response and decode result into a struct ### 194 195The Facebook Graph API always uses snake case keys in API response. 196This package can automatically convert from snake case to Go's camel-case-style style struct field names. 197 198For instance, to decode following JSON response... 199 200```json 201{ 202 "foo_bar": "player" 203} 204``` 205 206One can use following struct. 207 208```go 209type Data struct { 210 FooBar string // "FooBar" maps to "foo_bar" in JSON automatically in this case. 211} 212``` 213 214The decoding of each struct field can be customized by the format string stored under the `facebook` key or the "json" key in the struct field's tag. The `facebook` key is recommended as it's specifically designed for this package. 215 216Following is a sample shows all possible field tags. 217 218```go 219// define a Facebook feed object. 220type FacebookFeed struct { 221 Id string `facebook:",required"` // this field must exist in response. 222 // mind the "," before "required". 223 Story string 224 FeedFrom *FacebookFeedFrom `facebook:"from"` // use customized field name "from". 225 CreatedTime string `facebook:"created_time,required"` // both customized field name and "required" flag. 226 Omitted string `facebook:"-"` // this field is omitted when decoding. 227} 228 229type FacebookFeedFrom struct { 230 Name string `json:"name"` // the "json" key also works as expected. 231 Id string `facebook:"id" json:"shadowed"` // if both "facebook" and "json" key are set, the "facebook" key is used. 232} 233 234// create a feed object direct from Graph API result. 235var feed FacebookFeed 236res, _ := session.Get("/me/feed", nil) 237res.DecodeField("data.0", &feed) // read latest feed 238``` 239 240### Send a batch request ### 241 242```go 243params1 := Params{ 244 "method": fb.GET, 245 "relative_url": "me", 246} 247params2 := Params{ 248 "method": fb.GET, 249 "relative_url": uint64(100002828925788), 250} 251results, err := fb.BatchApi(your_access_token, params1, params2) 252 253if err != nil { 254 // check error... 255 return 256} 257 258// batchResult1 and batchResult2 are response for params1 and params2. 259batchResult1, _ := results[0].Batch() 260batchResult2, _ := results[1].Batch() 261 262// Use parsed result. 263var id string 264res := batchResult1.Result 265res.DecodeField("id", &id) 266 267// Use response header. 268contentType := batchResult1.Header.Get("Content-Type") 269``` 270 271### Using with Google App Engine ### 272 273Google App Engine provides the `appengine/urlfetch` package as the standard HTTP client package. 274For this reason, the default client in `net/http` won't work. 275One must explicitly set the HTTP client in `Session` to make it work. 276 277```go 278import ( 279 "appengine" 280 "appengine/urlfetch" 281) 282 283// suppose it's the appengine context initialized somewhere. 284var context appengine.Context 285 286// default Session object uses http.DefaultClient which is not allowed to use 287// in appengine. one has to create a Session and assign it a special client. 288seesion := globalApp.Session("a-access-token") 289session.HttpClient = urlfetch.Client(context) 290 291// now, session uses appengine http client now. 292res, err := session.Get("/me", nil) 293``` 294 295### Select Graph API version ### 296 297See [Platform Versioning](https://developers.facebook.com/docs/apps/versions) to understand Facebook versioning strategy. 298 299```go 300// this package uses default version which is controlled by Facebook app setting. 301// change following global variable to specific a global default version. 302fb.Version = "v3.0" 303 304// starting with Graph API v2.0; it's not allowed to get user information without access token. 305fb.Api("huan.du", GET, nil) 306 307// it's possible to specify version per session. 308session := &fb.Session{} 309session.Version = "v3.0" // overwrite global default. 310``` 311 312### Enable `appsecret_proof` ### 313 314Facebook can verify Graph API Calls with `appsecret_proof`. It's a feature to make Graph API call more secure. See [Securing Graph API Requests](https://developers.facebook.com/docs/graph-api/securing-requests) to know more about it. 315 316```go 317globalApp := fb.New("your-app-id", "your-app-secret") 318 319// enable "appsecret_proof" for all sessions created by this app. 320globalApp.EnableAppsecretProof = true 321 322// all calls in this session are secured. 323session := globalApp.Session("a-valid-access-token") 324session.Get("/me", nil) 325 326// it's also possible to enable/disable this feature per session. 327session.EnableAppsecretProof(false) 328``` 329 330### Debugging API Requests ### 331 332Facebook has introduced a way to debug Graph API calls. See [Debugging API Requests](https://developers.facebook.com/docs/graph-api/using-graph-api/debugging) for more details. 333 334This package provides both a package level and per session debug flag. Set `Debug` to a `DEBUG_*` constant to change debug mode globally; or use `Session#SetDebug` to change debug mode for one session. 335 336When debug mode is turned on, use `Result#DebugInfo` to get `DebugInfo` struct from the result. 337 338```go 339fb.Debug = fb.DEBUG_ALL 340 341res, _ := fb.Get("/me", fb.Params{"access_token": "xxx"}) 342debugInfo := res.DebugInfo() 343 344fmt.Println("http headers:", debugInfo.Header) 345fmt.Println("facebook api version:", debugInfo.FacebookApiVersion) 346``` 347 348### Monitoring API usage info ### 349 350Call `Result#UsageInfo` to get a `UsageInfo` struct containing both app and page level rate limit information from the result. More information about rate limiting can be found [here](https://developers.facebook.com/docs/graph-api/advanced/rate-limiting). 351 352```go 353res, _ := fb.Get("/me", fb.Params{"access_token": "xxx"}) 354usageInfo := res.UsageInfo() 355 356fmt.Println("App level rate limit information:", usageInfo.App) 357fmt.Println("Page level rate limit information:", usageInfo.Page) 358``` 359 360### Work with package `golang.org/x/oauth2` ## 361 362The `golang.org/x/oauth2` package can handle the Facebook OAuth2 authentication process and access token quite well. This package can work with it by setting `Session#HttpClient` to OAuth2's client. 363 364```go 365import ( 366 "golang.org/x/oauth2" 367 oauth2fb "golang.org/x/oauth2/facebook" 368 fb "github.com/huandu/facebook" 369) 370 371// Get Facebook access token. 372conf := &oauth2.Config{ 373 ClientID: "AppId", 374 ClientSecret: "AppSecret", 375 RedirectURL: "CallbackURL", 376 Scopes: []string{"email"}, 377 Endpoint: oauth2fb.Endpoint, 378} 379token, err := conf.Exchange(oauth2.NoContext, "code") 380 381// Create a client to manage access token life cycle. 382client := conf.Client(oauth2.NoContext, token) 383 384// Use OAuth2 client with session. 385session := &fb.Session{ 386 Version: "v2.4", 387 HttpClient: client, 388} 389 390// Use session. 391res, _ := session.Get("/me", nil) 392``` 393 394### Control timeout and cancelation with `Context` ### 395 396The `Session` accept a `Context`. 397 398```go 399// Create a new context. 400ctx, cancel := context.WithTimeout(session.Context(), 100 * time.Millisecond) 401defer cancel() 402 403// Call an API with ctx. 404// The return value of `session.WithContext` is a shadow copy of original session and 405// should not be stored. It can be used only once. 406result, err := session.WithContext(ctx).Get("/me", nil) 407``` 408 409See [this Go blog post about context](https://blog.golang.org/context) for more details about how to use `Context`. 410 411## Change Log ## 412 413See [CHANGELOG.md](CHANGELOG.md). 414 415## Out of Scope ## 416 4171. No OAuth integration. This package only provides APIs to parse/verify access token and code generated in OAuth 2.0 authentication process. 4182. No old RESTful API and FQL support. Such APIs are deprecated for years. Forget about them. 419 420## License ## 421 422This package is licensed under the MIT license. See LICENSE for details. 423