updating make

This commit is contained in:
=Corey Hulen
2015-06-25 12:21:47 -04:00
parent 6ee153d7f8
commit 16505ffe94
17 changed files with 10 additions and 4856 deletions

13
Godeps/Godeps.json generated
View File

@@ -88,11 +88,6 @@
"ImportPath": "github.com/gorilla/websocket",
"Rev": "6fd0f867fef40c540fa05c59f86396de10a632a6"
},
{
"ImportPath": "github.com/huandu/facebook",
"Comment": "v1.5.2",
"Rev": "7c46d89211b9a3f01087f47f6a399b815801ade4"
},
{
"ImportPath": "github.com/mssola/user_agent",
"Comment": "v0.4.1-2-g35c7f18",
@@ -122,6 +117,10 @@
"ImportPath": "golang.org/x/crypto/blowfish",
"Rev": "74f810a0152f4c50a16195f6b9ff44afc35594e8"
},
{
"ImportPath": "golang.org/x/image/bmp",
"Rev": "eb11b45157c1b71f30b3cec66306f1cd779a689e"
},
{
"ImportPath": "gopkg.in/bufio.v1",
"Comment": "v1",
@@ -136,10 +135,6 @@
"ImportPath": "gopkg.in/redis.v2",
"Comment": "v2.3.2",
"Rev": "e6179049628164864e6e84e973cfb56335748dea"
},
{
"ImportPath": "golang.org/x/image/bmp",
"Rev": "eb11b45157c1b71f30b3cec66306f1cd779a689e"
}
]
}

View File

@@ -1,47 +0,0 @@
# Change Log #
## v1.5.2 ##
* `[FIX]` [#32](https://github.com/huandu/facebook/pull/32) BatchApi/Batch returns facebook error when access token is not valid.
## v1.5.1 ##
* `[FIX]` [#31](https://github.com/huandu/facebook/pull/31) When `/oauth/access_token` returns a query string instead of json, this package can correctly handle it.
## v1.5.0 ##
* `[NEW]` [#28](https://github.com/huandu/facebook/pull/28) Support debug mode introduced by facebook graph API v2.3.
* `[FIX]` Removed all test cases depending on facebook graph API v1.0.
## v1.4.1 ##
* `[NEW]` [#27](https://github.com/huandu/facebook/pull/27) Timestamp value in Graph API response can be decoded as a `time.Time` value now. Thanks, [@Lazyshot](https://github.com/Lazyshot).
## v1.4.0 ##
* `[FIX]` [#23](https://github.com/huandu/facebook/issues/24) Algorithm change: Camel case string to underscore string supports abbreviation
Fix for [#23](https://github.com/huandu/facebook/issues/24) could be a breaking change. Camel case string `HTTPServer` will be converted to `http_server` instead of `h_t_t_p_server`. See issue description for detail.
## v1.3.0 ##
* `[NEW]` [#22](https://github.com/huandu/facebook/issues/22) Add a new helper struct `BatchResult` to hold batch request responses.
## v1.2.0 ##
* `[NEW]` [#20](https://github.com/huandu/facebook/issues/20) Add Decode functionality for paging results. Thanks, [@cbroglie](https://github.com/cbroglie).
* `[FIX]` [#21](https://github.com/huandu/facebook/issues/21) `Session#Inspect` cannot return error if access token is invalid.
Fix for [#21](https://github.com/huandu/facebook/issues/21) will result a possible breaking change in `Session#Inspect`. It was return whole result returned by facebook inspect api. Now it only return its "data" sub-tree. As facebook puts everything including error message in "data" sub-tree, I believe it's reasonable to make this change.
## v1.1.0 ##
* `[FIX]` [#19](https://github.com/huandu/facebook/issues/19) Any valid int64 number larger than 2^53 or smaller than -2^53 can be correctly decoded without precision lost.
Fix for [#19](https://github.com/huandu/facebook/issues/19) will result a possible breaking change in `Result#Get` and `Result#GetField`. If a JSON field is a number, these two functions will return json.Number instead of float64.
The fix also introduces a side effect in `Result#Decode` and `Result#DecodeField`. A number field (`int*` and `float*`) can be decoded to a string. It was not allowed in previous version.
## v1.0.0 ##
Initial tag. Library is stable enough for all features mentioned in README.md.

View File

@@ -1,7 +0,0 @@
Thanks for contributing this project!
Please don't forget to use `gofmt` to make your code look good.
Here is the command I use. Please always use the same parameters.
go fmt

View File

@@ -1,19 +0,0 @@
Copyright (c) 2012 - 2015 Huan Du
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,347 +0,0 @@
# A Facebook Graph API SDK In Golang #
[![Build Status](https://travis-ci.org/huandu/facebook.png?branch=master)](https://travis-ci.org/huandu/facebook)
This is a Go package fully supports Facebook Graph API with file upload, batch request, FQL and multi-FQL. It can be used in Google App Engine.
API documents can be found on [godoc](http://godoc.org/github.com/huandu/facebook).
Feel 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.
## Get It ##
Use `go get -u github.com/huandu/facebook` to get or update it.
## Usage ##
### Quick start ###
Here is a sample to read my Facebook username by uid.
```go
package main
import (
"fmt"
fb "github.com/huandu/facebook"
)
func main() {
res, _ := fb.Get("/538744468", fb.Params{
"fields": "username",
"access_token": "a-valid-access-token",
})
fmt.Println("here is my facebook username:", res["username"])
}
```
Type of `res` is `fb.Result` (a.k.a. `map[string]interface{}`).
This type has several useful methods to decode `res` to any Go type safely.
```go
// Decode "username" to a Go string.
var username string
res.DecodeField("username", &username)
fmt.Println("alternative way to get username:", username)
// It's also possible to decode the whole result into a predefined struct.
type User struct {
Username string
}
var user User
res.Decode(&user)
fmt.Println("print username in struct:", user.Username)
```
### Read a graph `user` object with a valid access token ###
```go
res, err := fb.Get("/me/feed", fb.Params{
"access_token": "a-valid-access-token",
})
if err != nil {
// err can be an facebook API error.
// if so, the Error struct contains error details.
if e, ok := err.(*Error); ok {
fmt.Logf("facebook error. [message:%v] [type:%v] [code:%v] [subcode:%v]",
e.Message, e.Type, e.Code, e.ErrorSubcode)
return
}
return
}
// read my last feed.
fmt.Println("my latest feed story is:", res.Get("data.0.story"))
```
### Read a graph `search` for page and decode slice of maps
```go
res, _ := fb.Get("/search", fb.Params{
"access_token": "a-valid-access-token",
"type": "page",
"q": "nightlife,singapore",
})
var items []fb.Result
err := res.DecodeField("data", &items)
if err != nil {
fmt.Logf("An error has happened %v", err)
return
}
for _, item := range items {
fmt.Println(item["id"])
}
```
### Use `App` and `Session` ###
It's recommended to use `App` and `Session` in a production app. They provide more controls over all API calls. They can also make code clear and concise.
```go
// create a global App var to hold app id and secret.
var globalApp = fb.New("your-app-id", "your-app-secret")
// facebook asks for a valid redirect uri when parsing signed request.
// it's a new enforced policy starting in late 2013.
globalApp.RedirectUri = "http://your.site/canvas/url/"
// here comes a client with a facebook signed request string in query string.
// creates a new session with signed request.
session, _ := globalApp.SessionFromSignedRequest(signedRequest)
// if there is another way to get decoded access token,
// creates a session directly with the token.
session := globalApp.Session(token)
// validate access token. err is nil if token is valid.
err := session.Validate()
// use session to send api request with access token.
res, _ := session.Get("/me/feed", nil)
```
### Use `paging` field in response. ###
Some Graph API responses use a special JSON structure to provide paging information. Use `Result.Paging()` to walk through all data in such results.
```go
res, _ := session.Get("/me/home", nil)
// create a paging structure.
paging, _ := res.Paging(session)
// get current results.
results := paging.Data()
// get next page.
noMore, err := paging.Next()
results = paging.Data()
```
### Read graph api response and decode result into a struct ###
As facebook Graph API always uses lower case words as keys in API response.
This package can convert go's camel-case-style struct field name to facebook's underscore-style API key name.
For instance, to decode following JSON response...
```json
{
"foo_bar": "player"
}
```
One can use following struct.
```go
type Data struct {
FooBar string // "FooBar" maps to "foo_bar" in JSON automatically in this case.
}
```
Decoding behavior can be changed per field through field tag -- just like what `encoding/json` does.
Following is a sample shows all possible field tags.
```go
// define a facebook feed object.
type FacebookFeed struct {
Id string `facebook:",required"` // this field must exist in response.
// mind the "," before "required".
Story string
FeedFrom *FacebookFeedFrom `facebook:"from"` // use customized field name "from"
CreatedTime string `facebook:"created_time,required"` // both customized field name and "required" flag.
}
type FacebookFeedFrom struct {
Name, Id string
}
// create a feed object direct from graph api result.
var feed FacebookFeed
res, _ := session.Get("/me/feed", nil)
res.DecodeField("data.0", &feed) // read latest feed
```
### Send a batch request ###
```go
params1 := Params{
"method": fb.GET,
"relative_url": "me",
}
params2 := Params{
"method": fb.GET,
"relative_url": uint64(100002828925788),
}
results, err := fb.BatchApi(your_access_token, params1, params2)
if err != nil {
// check error...
return
}
// batchResult1 and batchResult2 are response for params1 and params2.
batchResult1, _ := results[0].Batch()
batchResult2, _ := results[1].Batch()
// Use parsed result.
var id string
res := batchResult1.Result
res.DecodeField("id", &id)
// Use response header.
contentType := batchResult1.Header.Get("Content-Type")
```
### Send FQL query ###
```go
results, _ := fb.FQL("SELECT username FROM page WHERE page_id = 20531316728")
fmt.Println(results[0]["username"]) // print "facebook"
// most FQL query requires access token. create session to hold access token.
session := &fb.Session{}
session.SetAccessToken("A-VALID-ACCESS-TOKEN")
results, _ := session.FQL("SELECT username FROM page WHERE page_id = 20531316728")
fmt.Println(results[0]["username"]) // print "facebook"
```
### Make multi-FQL ###
```go
res, _ := fb.MultiFQL(Params{
"query1": "SELECT username FROM page WHERE page_id = 20531316728",
"query2": "SELECT uid FROM user WHERE uid = 538744468",
})
var query1, query2 []Result
// get response for query1 and query2.
res.DecodeField("query1", &query1)
res.DecodeField("query2", &query2)
// most FQL query requires access token. create session to hold access token.
session := &fb.Session{}
session.SetAccessToken("A-VALID-ACCESS-TOKEN")
res, _ := session.MultiFQL(Params{
"query1": "...",
"query2": "...",
})
// same as the sample without access token...
```
### Use it in Google App Engine ###
Google App Engine provide `appengine/urlfetch` package as standard http client package. Default client in `net/http` doesn't work. One must explicitly set http client in `Session` to make it work.
```go
import (
"appengine"
"appengine/urlfetch"
)
// suppose it's the appengine context initialized somewhere.
var context appengine.Context
// default Session object uses http.DefaultClient which is not allowed to use
// in appengine. one has to create a Session and assign it a special client.
seesion := globalApp.Session("a-access-token")
session.HttpClient = urlfetch.Client(context)
// now, session uses appengine http client now.
res, err := session.Get("/me", nil)
```
### Select Graph API version ###
See [Platform Versioning](https://developers.facebook.com/docs/apps/versions) to understand facebook versioning strategy.
```go
// this package uses default version which is controlled by facebook app setting.
// change following global variable to specific a global default version.
fb.Version = "v2.0"
// starting with graph api v2.0, it's not allowed to get user information without access token.
fb.Api("huan.du", GET, nil)
// it's possible to specify version per session.
session := &fb.Session{}
session.Version = "v2.0" // overwrite global default.
```
### Enable `appsecret_proof` ###
Facebook 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.
```go
globalApp := fb.New("your-app-id", "your-app-secret")
// enable "appsecret_proof" for all sessions created by this app.
globalApp.EnableAppsecretProof = true
// all calls in this session are secured.
session := globalApp.Session("a-valid-access-token")
session.Get("/me", nil)
// it's also possible to enable/disable this feature per session.
session.EnableAppsecretProof(false)
```
### Debugging API Requests ###
Facebook introduces a way to debug graph API calls. See [Debugging API Requests](https://developers.facebook.com/docs/graph-api/using-graph-api/v2.3#debugging) for details.
This package provides both 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.
When debug mode is turned on, use `Result#DebugInfo` to get `DebugInfo` struct from result.
```go
fb.Debug = fb.DEBUG_ALL
res, _ := fb.Get("/me", fb.Params{"access_token": "xxx"})
debugInfo := res.DebugInfo()
fmt.Println("http headers:", debugInfo.Header)
fmt.Println("facebook api version:", debugInfo.FacebookApiVersion)
```
## Change Log ##
See [CHANGELOG.md](CHANGELOG.md).
## Out of Scope ##
1. No OAuth integration. This package only provides APIs to parse/verify access token and code generated in OAuth 2.0 authentication process.
2. No old RESTful API support. Such APIs are deprecated for years. Forget about them.
## License ##
This package is licensed under MIT license. See LICENSE for details.

View File

@@ -1,180 +0,0 @@
// A facebook graph api client in go.
// https://github.com/huandu/facebook/
//
// Copyright 2012 - 2015, Huan Du
// Licensed under the MIT license
// https://github.com/huandu/facebook/blob/master/LICENSE
// This is a Go library fully supports Facebook Graph API (both 1.0 and 2.x) with
// file upload, batch request, FQL and multi-FQL. It can be used in Google App Engine.
//
// Library design is highly influenced by facebook official PHP/JS SDK.
// If you have experience with PHP/JS SDK, you may feel quite familiar with it.
//
// Go to project home page to see samples. Link: https://github.com/huandu/facebook
//
// This library doesn't implement any deprecated old RESTful API. And it won't.
package facebook
import (
"net/http"
)
var (
// Default facebook api version.
// It can be any valid version string (e.g. "v2.3") or empty.
//
// See https://developers.facebook.com/docs/apps/versions for details.
Version string
// Set app level debug mode.
// After setting DebugMode, all newly created session will use the mode
// to communicate with graph API.
//
// See https://developers.facebook.com/docs/graph-api/using-graph-api/v2.3#debugging
Debug DebugMode
)
// Makes a facebook graph api call with default session.
//
// Method can be GET, POST, DELETE or PUT.
//
// Params represents query strings in this call.
// Keys and values in params will be encoded for URL automatically. So there is
// no need to encode keys or values in params manually. Params can be nil.
//
// If you want to get
// https://graph.facebook.com/huandu?fields=name,username
// Api should be called as following
// Api("/huandu", GET, Params{"fields": "name,username"})
// or in a simplified way
// Get("/huandu", Params{"fields": "name,username"})
//
// Api is a wrapper of Session.Api(). It's designed for graph api that doesn't require
// app id, app secret and access token. It can be called in multiple goroutines.
//
// If app id, app secret or access token is required in graph api, caller should
// create a new facebook session through App instance instead.
func Api(path string, method Method, params Params) (Result, error) {
return defaultSession.Api(path, method, params)
}
// Get is a short hand of Api(path, GET, params).
func Get(path string, params Params) (Result, error) {
return Api(path, GET, params)
}
// Post is a short hand of Api(path, POST, params).
func Post(path string, params Params) (Result, error) {
return Api(path, POST, params)
}
// Delete is a short hand of Api(path, DELETE, params).
func Delete(path string, params Params) (Result, error) {
return Api(path, DELETE, params)
}
// Put is a short hand of Api(path, PUT, params).
func Put(path string, params Params) (Result, error) {
return Api(path, PUT, params)
}
// Makes a batch facebook graph api call with default session.
//
// BatchApi supports most kinds of batch calls defines in facebook batch api document,
// except uploading binary data. Use Batch to do so.
//
// Note: API response is stored in "body" field of a Result.
// results, _ := BatchApi(accessToken, Params{...}, Params{...})
//
// // Use first batch api response.
// var res1 *BatchResult
// var err error
// res1, err = results[0].Batch()
//
// if err != nil {
// // this is not a valid batch api response.
// }
//
// // Use BatchResult#Result to get response body content as Result.
// res := res1.Result
//
// Facebook document: https://developers.facebook.com/docs/graph-api/making-multiple-requests
func BatchApi(accessToken string, params ...Params) ([]Result, error) {
return Batch(Params{"access_token": accessToken}, params...)
}
// Makes a batch facebook graph api call with default session.
// Batch is designed for more advanced usage including uploading binary files.
//
// An uploading files sample
// // equivalent to following curl command (borrowed from facebook docs)
// // curl \
// // -F 'access_token=…' \
// // -F 'batch=[{"method":"POST","relative_url":"me/photos","body":"message=My cat photo","attached_files":"file1"},{"method":"POST","relative_url":"me/photos","body":"message=My dog photo","attached_files":"file2"},]' \
// // -F 'file1=@cat.gif' \
// // -F 'file2=@dog.jpg' \
// // https://graph.facebook.com
// Batch(Params{
// "access_token": "the-access-token",
// "file1": File("cat.gif"),
// "file2": File("dog.jpg"),
// }, Params{
// "method": "POST",
// "relative_url": "me/photos",
// "body": "message=My cat photo",
// "attached_files": "file1",
// }, Params{
// "method": "POST",
// "relative_url": "me/photos",
// "body": "message=My dog photo",
// "attached_files": "file2",
// })
//
// Facebook document: https://developers.facebook.com/docs/graph-api/making-multiple-requests
func Batch(batchParams Params, params ...Params) ([]Result, error) {
return defaultSession.Batch(batchParams, params...)
}
// Makes a FQL query with default session.
// Returns a slice of Result. If there is no query result, the result is nil.
//
// FQL can only make query without "access_token". For query requiring "access_token", create
// Session and call its FQL method.
//
// Facebook document: https://developers.facebook.com/docs/technical-guides/fql#query
func FQL(query string) ([]Result, error) {
return defaultSession.FQL(query)
}
// Makes a multi FQL query with default session.
// Returns a parsed Result. The key is the multi query key, and the value is the query result.
//
// MultiFQL can only make query without "access_token". For query requiring "access_token", create
// Session and call its MultiFQL method.
//
// See Session.MultiFQL document for samples.
//
// Facebook document: https://developers.facebook.com/docs/technical-guides/fql#multi
func MultiFQL(queries Params) (Result, error) {
return defaultSession.MultiFQL(queries)
}
// Makes an arbitrary HTTP request with default session.
// It expects server responses a facebook Graph API response.
// request, _ := http.NewRequest("https://graph.facebook.com/538744468", "GET", nil)
// res, err := Request(request)
// fmt.Println(res["gender"]) // get "male"
func Request(request *http.Request) (Result, error) {
return defaultSession.Request(request)
}
// DefaultHttpClient returns the http client for default session.
func DefaultHttpClient() HttpClient {
return defaultSession.HttpClient
}
// SetHttpClient updates the http client of default session.
func SetHttpClient(client HttpClient) {
defaultSession.HttpClient = client
}

View File

@@ -1,255 +0,0 @@
// A facebook graph api client in go.
// https://github.com/huandu/facebook/
//
// Copyright 2012 - 2015, Huan Du
// Licensed under the MIT license
// https://github.com/huandu/facebook/blob/master/LICENSE
package facebook
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/json"
"fmt"
"strings"
)
// Creates a new App and sets app id and secret.
func New(appId, appSecret string) *App {
return &App{
AppId: appId,
AppSecret: appSecret,
}
}
// Gets application access token, useful for gathering public information about users and applications.
func (app *App) AppAccessToken() string {
return app.AppId + "|" + app.AppSecret
}
// Parses signed request.
func (app *App) ParseSignedRequest(signedRequest string) (res Result, err error) {
strs := strings.SplitN(signedRequest, ".", 2)
if len(strs) != 2 {
err = fmt.Errorf("invalid signed request format.")
return
}
sig, e1 := decodeBase64URLEncodingString(strs[0])
if e1 != nil {
err = fmt.Errorf("cannot decode signed request sig. error is %v.", e1)
return
}
payload, e2 := decodeBase64URLEncodingString(strs[1])
if e2 != nil {
err = fmt.Errorf("cannot decode signed request payload. error is %v.", e2)
return
}
err = json.Unmarshal(payload, &res)
if err != nil {
err = fmt.Errorf("signed request payload is not a valid json string. error is %v.", err)
return
}
var hashMethod string
err = res.DecodeField("algorithm", &hashMethod)
if err != nil {
err = fmt.Errorf("signed request payload doesn't contains a valid 'algorithm' field.")
return
}
hashMethod = strings.ToUpper(hashMethod)
if hashMethod != "HMAC-SHA256" {
err = fmt.Errorf("signed request payload uses an unknown HMAC method. expect 'HMAC-SHA256'. actual '%v'.", hashMethod)
return
}
hash := hmac.New(sha256.New, []byte(app.AppSecret))
hash.Write([]byte(strs[1])) // note: here uses the payload base64 string, not decoded bytes
expectedSig := hash.Sum(nil)
if bytes.Compare(sig, expectedSig) != 0 {
err = fmt.Errorf("bad signed request signiture.")
return
}
return
}
// ParseCode redeems code for a valid access token.
// It's a shorthand call to ParseCodeInfo(code, "").
//
// In facebook PHP SDK, there is a CSRF state to avoid attack.
// That state is not checked in this library.
// Caller is responsible to store and check state if possible.
func (app *App) ParseCode(code string) (token string, err error) {
token, _, _, err = app.ParseCodeInfo(code, "")
return
}
// ParseCodeInfo redeems code for access token and returns extra information.
// The machineId is optional.
//
// See https://developers.facebook.com/docs/facebook-login/access-tokens#extending
func (app *App) ParseCodeInfo(code, machineId string) (token string, expires int, newMachineId string, err error) {
if code == "" {
err = fmt.Errorf("code is empty")
return
}
var res Result
res, err = defaultSession.sendOauthRequest("/oauth/access_token", Params{
"client_id": app.AppId,
"redirect_uri": app.RedirectUri,
"code": code,
})
if err != nil {
err = fmt.Errorf("cannot parse facebook response. error is %v.", err)
return
}
err = res.DecodeField("access_token", &token)
if err != nil {
return
}
err = res.DecodeField("expires_in", &expires)
if err != nil {
return
}
if _, ok := res["machine_id"]; ok {
err = res.DecodeField("machine_id", &newMachineId)
}
return
}
// Exchange a short lived access token to a long lived access token.
// Return new access token and its expires time.
func (app *App) ExchangeToken(accessToken string) (token string, expires int, err error) {
if accessToken == "" {
err = fmt.Errorf("short lived accessToken is empty")
return
}
var res Result
res, err = defaultSession.sendOauthRequest("/oauth/access_token", Params{
"grant_type": "fb_exchange_token",
"client_id": app.AppId,
"client_secret": app.AppSecret,
"fb_exchange_token": accessToken,
})
if err != nil {
err = fmt.Errorf("cannot parse facebook response. error is %v.", err)
return
}
err = res.DecodeField("access_token", &token)
if err != nil {
return
}
err = res.DecodeField("expires_in", &expires)
return
}
// Get code from a long lived access token.
// Return the code retrieved from facebook.
func (app *App) GetCode(accessToken string) (code string, err error) {
if accessToken == "" {
err = fmt.Errorf("long lived accessToken is empty")
return
}
var res Result
res, err = defaultSession.sendOauthRequest("/oauth/client_code", Params{
"client_id": app.AppId,
"client_secret": app.AppSecret,
"redirect_uri": app.RedirectUri,
"access_token": accessToken,
})
if err != nil {
err = fmt.Errorf("cannot get code from facebook. error is %v.", err)
return
}
err = res.DecodeField("code", &code)
return
}
// Creates a session based on current App setting.
func (app *App) Session(accessToken string) *Session {
return &Session{
accessToken: accessToken,
app: app,
enableAppsecretProof: app.EnableAppsecretProof,
}
}
// Creates a session from a signed request.
// If signed request contains a code, it will automatically use this code
// to exchange a valid access token.
func (app *App) SessionFromSignedRequest(signedRequest string) (session *Session, err error) {
var res Result
res, err = app.ParseSignedRequest(signedRequest)
if err != nil {
return
}
var id, token string
res.DecodeField("user_id", &id) // it's ok without user id.
err = res.DecodeField("oauth_token", &token)
if err == nil {
session = &Session{
accessToken: token,
app: app,
id: id,
enableAppsecretProof: app.EnableAppsecretProof,
}
return
}
// cannot get "oauth_token"? try to get "code".
err = res.DecodeField("code", &token)
if err != nil {
// no code? no way to continue.
err = fmt.Errorf("cannot find 'oauth_token' and 'code'. no way to continue.")
return
}
token, err = app.ParseCode(token)
if err != nil {
return
}
session = &Session{
accessToken: token,
app: app,
id: id,
enableAppsecretProof: app.EnableAppsecretProof,
}
return
}

View File

@@ -1,52 +0,0 @@
// A facebook graph api client in go.
// https://github.com/huandu/facebook/
//
// Copyright 2012 - 2015, Huan Du
// Licensed under the MIT license
// https://github.com/huandu/facebook/blob/master/LICENSE
package facebook
import (
"encoding/json"
"net/http"
)
type batchResultHeader struct {
Name string `facebook=",required"`
Value string `facebook=",required"`
}
type batchResultData struct {
Code int `facebook=",required"`
Headers []batchResultHeader `facebook=",required"`
Body string `facebook=",required"`
}
func newBatchResult(res Result) (*BatchResult, error) {
var data batchResultData
err := res.Decode(&data)
if err != nil {
return nil, err
}
result := &BatchResult{
StatusCode: data.Code,
Header: http.Header{},
Body: data.Body,
}
err = json.Unmarshal([]byte(result.Body), &result.Result)
if err != nil {
return nil, err
}
// add headers to result.
for _, header := range data.Headers {
result.Header.Add(header.Name, header.Value)
}
return result, nil
}

View File

@@ -1,74 +0,0 @@
// A facebook graph api client in go.
// https://github.com/huandu/facebook/
//
// Copyright 2012 - 2015, Huan Du
// Licensed under the MIT license
// https://github.com/huandu/facebook/blob/master/LICENSE
package facebook
import (
"encoding/json"
"reflect"
"regexp"
"time"
)
// Facebook graph api methods.
const (
GET Method = "GET"
POST Method = "POST"
DELETE Method = "DELETE"
PUT Method = "PUT"
)
const (
ERROR_CODE_UNKNOWN = -1 // unknown facebook graph api error code.
_MIME_FORM_URLENCODED = "application/x-www-form-urlencoded"
)
// Graph API debug mode values.
const (
DEBUG_OFF DebugMode = "" // turn off debug.
DEBUG_ALL DebugMode = "all"
DEBUG_INFO DebugMode = "info"
DEBUG_WARNING DebugMode = "warning"
)
const (
debugInfoKey = "__debug__"
debugProtoKey = "__proto__"
debugHeaderKey = "__header__"
facebookApiVersionHeader = "facebook-api-version"
facebookDebugHeader = "x-fb-debug"
facebookRevHeader = "x-fb-rev"
)
var (
// Maps aliases to Facebook domains.
// Copied from Facebook PHP SDK.
domainMap = map[string]string{
"api": "https://api.facebook.com/",
"api_video": "https://api-video.facebook.com/",
"api_read": "https://api-read.facebook.com/",
"graph": "https://graph.facebook.com/",
"graph_video": "https://graph-video.facebook.com/",
"www": "https://www.facebook.com/",
}
// checks whether it's a video post.
regexpIsVideoPost = regexp.MustCompile(`/^(\/)(.+)(\/)(videos)$/`)
// default facebook session.
defaultSession = &Session{}
typeOfPointerToBinaryData = reflect.TypeOf(&binaryData{})
typeOfPointerToBinaryFile = reflect.TypeOf(&binaryFile{})
typeOfJSONNumber = reflect.TypeOf(json.Number(""))
typeOfTime = reflect.TypeOf(time.Time{})
facebookSuccessJsonBytes = []byte("true")
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,131 +0,0 @@
// A facebook graph api client in go.
// https://github.com/huandu/facebook/
//
// Copyright 2012 - 2015, Huan Du
// Licensed under the MIT license
// https://github.com/huandu/facebook/blob/master/LICENSE
package facebook
import (
"bytes"
"io"
"unicode"
"unicode/utf8"
)
func camelCaseToUnderScore(str string) string {
if len(str) == 0 {
return ""
}
buf := &bytes.Buffer{}
var prev, r0, r1 rune
var size int
r0 = '_'
for len(str) > 0 {
prev = r0
r0, size = utf8.DecodeRuneInString(str)
str = str[size:]
switch {
case r0 == utf8.RuneError:
buf.WriteByte(byte(str[0]))
case unicode.IsUpper(r0):
if prev != '_' {
buf.WriteRune('_')
}
buf.WriteRune(unicode.ToLower(r0))
if len(str) == 0 {
break
}
r0, size = utf8.DecodeRuneInString(str)
str = str[size:]
if !unicode.IsUpper(r0) {
buf.WriteRune(r0)
break
}
// find next non-upper-case character and insert `_` properly.
// it's designed to convert `HTTPServer` to `http_server`.
// if there are more than 2 adjacent upper case characters in a word,
// treat them as an abbreviation plus a normal word.
for len(str) > 0 {
r1 = r0
r0, size = utf8.DecodeRuneInString(str)
str = str[size:]
if r0 == utf8.RuneError {
buf.WriteRune(unicode.ToLower(r1))
buf.WriteByte(byte(str[0]))
break
}
if !unicode.IsUpper(r0) {
if r0 == '_' || r0 == ' ' || r0 == '-' {
r0 = '_'
buf.WriteRune(unicode.ToLower(r1))
} else {
buf.WriteRune('_')
buf.WriteRune(unicode.ToLower(r1))
buf.WriteRune(r0)
}
break
}
buf.WriteRune(unicode.ToLower(r1))
}
if len(str) == 0 || r0 == '_' {
buf.WriteRune(unicode.ToLower(r0))
break
}
default:
if r0 == ' ' || r0 == '-' {
r0 = '_'
}
buf.WriteRune(r0)
}
}
return buf.String()
}
// Returns error string.
func (e *Error) Error() string {
return e.Message
}
// Creates a new binary data holder.
func Data(filename string, source io.Reader) *binaryData {
return &binaryData{
Filename: filename,
Source: source,
}
}
// Creates a binary file holder.
func File(filename, path string) *binaryFile {
return &binaryFile{
Filename: filename,
}
}
// Creates a binary file holder and specific a different path for reading.
func FileAlias(filename, path string) *binaryFile {
return &binaryFile{
Filename: filename,
Path: path,
}
}

View File

@@ -1,146 +0,0 @@
// A facebook graph api client in go.
// https://github.com/huandu/facebook/
//
// Copyright 2012 - 2015, Huan Du
// Licensed under the MIT license
// https://github.com/huandu/facebook/blob/master/LICENSE
package facebook
import (
"bytes"
"fmt"
"net/http"
)
type pagingData struct {
Data []Result `facebook:",required"`
Paging *pagingNavigator
}
type pagingNavigator struct {
Previous string
Next string
}
func newPagingResult(session *Session, res Result) (*PagingResult, error) {
// quick check whether Result is a paging response.
if _, ok := res["data"]; !ok {
return nil, fmt.Errorf("current Result is not a paging response.")
}
pr := &PagingResult{
session: session,
}
paging := &pr.paging
err := res.Decode(paging)
if err != nil {
return nil, err
}
if paging.Paging != nil {
pr.previous = paging.Paging.Previous
pr.next = paging.Paging.Next
}
return pr, nil
}
// Get current data.
func (pr *PagingResult) Data() []Result {
return pr.paging.Data
}
// Decodes the current full result to a struct. See Result#Decode.
func (pr *PagingResult) Decode(v interface{}) (err error) {
res := Result{
"data": pr.Data(),
}
return res.Decode(v)
}
// Read previous page.
func (pr *PagingResult) Previous() (noMore bool, err error) {
if !pr.HasPrevious() {
noMore = true
return
}
return pr.navigate(&pr.previous)
}
// Read next page.
func (pr *PagingResult) Next() (noMore bool, err error) {
if !pr.HasNext() {
noMore = true
return
}
return pr.navigate(&pr.next)
}
// Check whether there is previous page.
func (pr *PagingResult) HasPrevious() bool {
return pr.previous != ""
}
// Check whether there is next page.
func (pr *PagingResult) HasNext() bool {
return pr.next != ""
}
func (pr *PagingResult) navigate(url *string) (noMore bool, err error) {
var pagingUrl string
// add session information in paging url.
params := Params{}
pr.session.prepareParams(params)
if len(params) == 0 {
pagingUrl = *url
} else {
buf := &bytes.Buffer{}
buf.WriteString(*url)
buf.WriteRune('&')
params.Encode(buf)
pagingUrl = buf.String()
}
var request *http.Request
var res Result
request, err = http.NewRequest("GET", pagingUrl, nil)
if err != nil {
return
}
res, err = pr.session.Request(request)
if err != nil {
return
}
if pr.paging.Paging != nil {
pr.paging.Paging.Next = ""
pr.paging.Paging.Previous = ""
}
paging := &pr.paging
err = res.Decode(paging)
if err != nil {
return
}
if paging.Paging == nil || len(paging.Data) == 0 {
*url = ""
noMore = true
} else {
pr.previous = paging.Paging.Previous
pr.next = paging.Paging.Next
}
return
}

View File

@@ -1,227 +0,0 @@
// A facebook graph api client in go.
// https://github.com/huandu/facebook/
//
// Copyright 2012 - 2015, Huan Du
// Licensed under the MIT license
// https://github.com/huandu/facebook/blob/master/LICENSE
package facebook
import (
"encoding/json"
"io"
"mime/multipart"
"net/url"
"os"
"reflect"
"runtime"
)
// Makes a new Params instance by given data.
// Data must be a struct or a map with string keys.
// MakeParams will change all struct field name to lower case name with underscore.
// e.g. "FooBar" will be changed to "foo_bar".
//
// Returns nil if data cannot be used to make a Params instance.
func MakeParams(data interface{}) (params Params) {
if p, ok := data.(Params); ok {
return p
}
defer func() {
if r := recover(); r != nil {
if _, ok := r.(runtime.Error); ok {
panic(r)
}
params = nil
}
}()
params = makeParams(reflect.ValueOf(data))
return
}
func makeParams(value reflect.Value) (params Params) {
for value.Kind() == reflect.Ptr || value.Kind() == reflect.Interface {
value = value.Elem()
}
// only map with string keys can be converted to Params
if value.Kind() == reflect.Map && value.Type().Key().Kind() == reflect.String {
params = Params{}
for _, key := range value.MapKeys() {
params[key.String()] = value.MapIndex(key).Interface()
}
return
}
if value.Kind() != reflect.Struct {
return
}
params = Params{}
num := value.NumField()
for i := 0; i < num; i++ {
name := camelCaseToUnderScore(value.Type().Field(i).Name)
field := value.Field(i)
for field.Kind() == reflect.Ptr {
field = field.Elem()
}
switch field.Kind() {
case reflect.Chan, reflect.Func, reflect.UnsafePointer, reflect.Invalid:
// these types won't be marshalled in json.
params = nil
return
default:
params[name] = field.Interface()
}
}
return
}
// Encodes params to query string.
// If map value is not a string, Encode uses json.Marshal() to convert value to string.
//
// Encode will panic if Params contains values that cannot be marshalled to json string.
func (params Params) Encode(writer io.Writer) (mime string, err error) {
if params == nil || len(params) == 0 {
mime = _MIME_FORM_URLENCODED
return
}
// check whether params contains any binary data.
hasBinary := false
for _, v := range params {
typ := reflect.TypeOf(v)
if typ == typeOfPointerToBinaryData || typ == typeOfPointerToBinaryFile {
hasBinary = true
break
}
}
if hasBinary {
return params.encodeMultipartForm(writer)
}
return params.encodeFormUrlEncoded(writer)
}
func (params Params) encodeFormUrlEncoded(writer io.Writer) (mime string, err error) {
var jsonStr []byte
written := false
for k, v := range params {
if written {
io.WriteString(writer, "&")
}
io.WriteString(writer, url.QueryEscape(k))
io.WriteString(writer, "=")
if reflect.TypeOf(v).Kind() == reflect.String {
io.WriteString(writer, url.QueryEscape(reflect.ValueOf(v).String()))
} else {
jsonStr, err = json.Marshal(v)
if err != nil {
return
}
io.WriteString(writer, url.QueryEscape(string(jsonStr)))
}
written = true
}
mime = _MIME_FORM_URLENCODED
return
}
func (params Params) encodeMultipartForm(writer io.Writer) (mime string, err error) {
w := multipart.NewWriter(writer)
defer func() {
w.Close()
mime = w.FormDataContentType()
}()
for k, v := range params {
switch value := v.(type) {
case *binaryData:
var dst io.Writer
dst, err = w.CreateFormFile(k, value.Filename)
if err != nil {
return
}
_, err = io.Copy(dst, value.Source)
if err != nil {
return
}
case *binaryFile:
var dst io.Writer
var file *os.File
var path string
dst, err = w.CreateFormFile(k, value.Filename)
if err != nil {
return
}
if value.Path == "" {
path = value.Filename
} else {
path = value.Path
}
file, err = os.Open(path)
if err != nil {
return
}
_, err = io.Copy(dst, file)
if err != nil {
return
}
default:
var dst io.Writer
var jsonStr []byte
dst, err = w.CreateFormField(k)
if reflect.TypeOf(v).Kind() == reflect.String {
io.WriteString(dst, reflect.ValueOf(v).String())
} else {
jsonStr, err = json.Marshal(v)
if err != nil {
return
}
_, err = dst.Write(jsonStr)
if err != nil {
return
}
}
}
}
return
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,667 +0,0 @@
// A facebook graph api client in go.
// https://github.com/huandu/facebook/
//
// Copyright 2012 - 2015, Huan Du
// Licensed under the MIT license
// https://github.com/huandu/facebook/blob/master/LICENSE
package facebook
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
// Makes a facebook graph api call.
//
// If session access token is set, "access_token" in params will be set to the token value.
//
// Returns facebook graph api call result.
// If facebook returns error in response, returns error details in res and set err.
func (session *Session) Api(path string, method Method, params Params) (Result, error) {
return session.graph(path, method, params)
}
// Get is a short hand of Api(path, GET, params).
func (session *Session) Get(path string, params Params) (Result, error) {
return session.Api(path, GET, params)
}
// Post is a short hand of Api(path, POST, params).
func (session *Session) Post(path string, params Params) (Result, error) {
return session.Api(path, POST, params)
}
// Delete is a short hand of Api(path, DELETE, params).
func (session *Session) Delete(path string, params Params) (Result, error) {
return session.Api(path, DELETE, params)
}
// Put is a short hand of Api(path, PUT, params).
func (session *Session) Put(path string, params Params) (Result, error) {
return session.Api(path, PUT, params)
}
// Makes a batch call. Each params represent a single facebook graph api call.
//
// BatchApi supports most kinds of batch calls defines in facebook batch api document,
// except uploading binary data. Use Batch to upload binary data.
//
// If session access token is set, the token will be used in batch api call.
//
// Returns an array of batch call result on success.
//
// Facebook document: https://developers.facebook.com/docs/graph-api/making-multiple-requests
func (session *Session) BatchApi(params ...Params) ([]Result, error) {
return session.Batch(nil, params...)
}
// Makes a batch facebook graph api call.
// Batch is designed for more advanced usage including uploading binary files.
//
// If session access token is set, "access_token" in batchParams will be set to the token value.
//
// Facebook document: https://developers.facebook.com/docs/graph-api/making-multiple-requests
func (session *Session) Batch(batchParams Params, params ...Params) ([]Result, error) {
return session.graphBatch(batchParams, params...)
}
// Makes a FQL query.
// Returns a slice of Result. If there is no query result, the result is nil.
//
// Facebook document: https://developers.facebook.com/docs/technical-guides/fql#query
func (session *Session) FQL(query string) ([]Result, error) {
res, err := session.graphFQL(Params{
"q": query,
})
if err != nil {
return nil, err
}
// query result is stored in "data" field.
var data []Result
err = res.DecodeField("data", &data)
if err != nil {
return nil, err
}
return data, nil
}
// Makes a multi FQL query.
// Returns a parsed Result. The key is the multi query key, and the value is the query result.
//
// Here is a multi-query sample.
//
// res, _ := session.MultiFQL(Params{
// "query1": "SELECT name FROM user WHERE uid = me()",
// "query2": "SELECT uid1, uid2 FROM friend WHERE uid1 = me()",
// })
//
// // Get query results from response.
// var query1, query2 []Result
// res.DecodeField("query1", &query1)
// res.DecodeField("query2", &query2)
//
// Facebook document: https://developers.facebook.com/docs/technical-guides/fql#multi
func (session *Session) MultiFQL(queries Params) (Result, error) {
res, err := session.graphFQL(Params{
"q": queries,
})
if err != nil {
return res, err
}
// query result is stored in "data" field.
var data []Result
err = res.DecodeField("data", &data)
if err != nil {
return nil, err
}
if data == nil {
return nil, fmt.Errorf("multi-fql result is not found.")
}
// Multi-fql data structure is:
// {
// "data": [
// {
// "name": "query1",
// "fql_result_set": [
// {...}, {...}, ...
// ]
// },
// {
// "name": "query2",
// "fql_result_set": [
// {...}, {...}, ...
// ]
// },
// ...
// ]
// }
//
// Parse the structure to following go map.
// {
// "query1": [
// // Come from field "fql_result_set".
// {...}, {...}, ...
// ],
// "query2": [
// {...}, {...}, ...
// ],
// ...
// }
var name string
var apiResponse interface{}
var ok bool
result := Result{}
for k, v := range data {
err = v.DecodeField("name", &name)
if err != nil {
return nil, fmt.Errorf("missing required field 'name' in multi-query data.%v. %v", k, err)
}
apiResponse, ok = v["fql_result_set"]
if !ok {
return nil, fmt.Errorf("missing required field 'fql_result_set' in multi-query data.%v.", k)
}
result[name] = apiResponse
}
return result, nil
}
// Makes an arbitrary HTTP request.
// It expects server responses a facebook Graph API response.
// request, _ := http.NewRequest("https://graph.facebook.com/538744468", "GET", nil)
// res, err := session.Request(request)
// fmt.Println(res["gender"]) // get "male"
func (session *Session) Request(request *http.Request) (res Result, err error) {
var response *http.Response
var data []byte
response, data, err = session.sendRequest(request)
if err != nil {
return
}
res, err = MakeResult(data)
session.addDebugInfo(res, response)
if res != nil {
err = res.Err()
}
return
}
// Gets current user id from access token.
//
// Returns error if access token is not set or invalid.
//
// It's a standard way to validate a facebook access token.
func (session *Session) User() (id string, err error) {
if session.id != "" {
id = session.id
return
}
if session.accessToken == "" {
err = fmt.Errorf("access token is not set.")
return
}
var result Result
result, err = session.Api("/me", GET, Params{"fields": "id"})
if err != nil {
return
}
err = result.DecodeField("id", &id)
if err != nil {
return
}
return
}
// Validates Session access token.
// Returns nil if access token is valid.
func (session *Session) Validate() (err error) {
if session.accessToken == "" {
err = fmt.Errorf("access token is not set.")
return
}
var result Result
result, err = session.Api("/me", GET, Params{"fields": "id"})
if err != nil {
return
}
if f := result.Get("id"); f == nil {
err = fmt.Errorf("invalid access token.")
return
}
return
}
// Inspect Session access token.
// Returns JSON array containing data about the inspected token.
// See https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.2#checktoken
func (session *Session) Inspect() (result Result, err error) {
if session.accessToken == "" {
err = fmt.Errorf("access token is not set.")
return
}
if session.app == nil {
err = fmt.Errorf("cannot inspect access token without binding an app.")
return
}
appAccessToken := session.app.AppAccessToken()
if appAccessToken == "" {
err = fmt.Errorf("app access token is not set.")
return
}
result, err = session.Api("/debug_token", GET, Params{
"input_token": session.accessToken,
"access_token": appAccessToken,
})
if err != nil {
return
}
// facebook stores everything, including error, inside result["data"].
// make sure that result["data"] exists and doesn't contain error.
if _, ok := result["data"]; !ok {
err = fmt.Errorf("facebook inspect api returns unexpected result.")
return
}
var data Result
result.DecodeField("data", &data)
result = data
err = result.Err()
return
}
// Gets current access token.
func (session *Session) AccessToken() string {
return session.accessToken
}
// Sets a new access token.
func (session *Session) SetAccessToken(token string) {
if token != session.accessToken {
session.id = ""
session.accessToken = token
session.appsecretProof = ""
}
}
// Check appsecret proof is enabled or not.
func (session *Session) AppsecretProof() string {
if !session.enableAppsecretProof {
return ""
}
if session.accessToken == "" || session.app == nil {
return ""
}
if session.appsecretProof == "" {
hash := hmac.New(sha256.New, []byte(session.app.AppSecret))
hash.Write([]byte(session.accessToken))
session.appsecretProof = hex.EncodeToString(hash.Sum(nil))
}
return session.appsecretProof
}
// Enable or disable appsecret proof status.
// Returns error if there is no App associasted with this Session.
func (session *Session) EnableAppsecretProof(enabled bool) error {
if session.app == nil {
return fmt.Errorf("cannot change appsecret proof status without an associated App.")
}
if session.enableAppsecretProof != enabled {
session.enableAppsecretProof = enabled
// reset pre-calculated proof here to give caller a way to do so in some rare case,
// e.g. associated app's secret is changed.
session.appsecretProof = ""
}
return nil
}
// Gets associated App.
func (session *Session) App() *App {
return session.app
}
// Debug returns current debug mode.
func (session *Session) Debug() DebugMode {
if session.debug != DEBUG_OFF {
return session.debug
}
return Debug
}
// SetDebug updates per session debug mode and returns old mode.
// If per session debug mode is DEBUG_OFF, session will use global
// Debug mode.
func (session *Session) SetDebug(debug DebugMode) DebugMode {
old := session.debug
session.debug = debug
return old
}
func (session *Session) graph(path string, method Method, params Params) (res Result, err error) {
var graphUrl string
if params == nil {
params = Params{}
}
// always format as json.
params["format"] = "json"
// overwrite method as we always use post
params["method"] = method
// get graph api url.
if session.isVideoPost(path, method) {
graphUrl = session.getUrl("graph_video", path, nil)
} else {
graphUrl = session.getUrl("graph", path, nil)
}
var response *http.Response
response, err = session.sendPostRequest(graphUrl, params, &res)
session.addDebugInfo(res, response)
if res != nil {
err = res.Err()
}
return
}
func (session *Session) graphBatch(batchParams Params, params ...Params) ([]Result, error) {
if batchParams == nil {
batchParams = Params{}
}
batchParams["batch"] = params
var res []Result
graphUrl := session.getUrl("graph", "", nil)
_, err := session.sendPostRequest(graphUrl, batchParams, &res)
return res, err
}
func (session *Session) graphFQL(params Params) (res Result, err error) {
if params == nil {
params = Params{}
}
session.prepareParams(params)
// encode url.
buf := &bytes.Buffer{}
buf.WriteString(domainMap["graph"])
buf.WriteString("fql?")
_, err = params.Encode(buf)
if err != nil {
return nil, fmt.Errorf("cannot encode params. %v", err)
}
// it seems facebook disallow POST to /fql. always use GET for FQL.
var response *http.Response
response, err = session.sendGetRequest(buf.String(), &res)
session.addDebugInfo(res, response)
if res != nil {
err = res.Err()
}
return
}
func (session *Session) prepareParams(params Params) {
if _, ok := params["access_token"]; !ok && session.accessToken != "" {
params["access_token"] = session.accessToken
}
if session.enableAppsecretProof && session.accessToken != "" && session.app != nil {
params["appsecret_proof"] = session.AppsecretProof()
}
debug := session.Debug()
if debug != DEBUG_OFF {
params["debug"] = debug
}
}
func (session *Session) sendGetRequest(uri string, res interface{}) (*http.Response, error) {
request, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, err
}
response, data, err := session.sendRequest(request)
if err != nil {
return response, err
}
err = makeResult(data, res)
return response, err
}
func (session *Session) sendPostRequest(uri string, params Params, res interface{}) (*http.Response, error) {
session.prepareParams(params)
buf := &bytes.Buffer{}
mime, err := params.Encode(buf)
if err != nil {
return nil, fmt.Errorf("cannot encode POST params. %v", err)
}
var request *http.Request
request, err = http.NewRequest("POST", uri, buf)
if err != nil {
return nil, err
}
request.Header.Set("Content-Type", mime)
response, data, err := session.sendRequest(request)
if err != nil {
return response, err
}
err = makeResult(data, res)
return response, err
}
func (session *Session) sendOauthRequest(uri string, params Params) (Result, error) {
urlStr := session.getUrl("graph", uri, nil)
buf := &bytes.Buffer{}
mime, err := params.Encode(buf)
if err != nil {
return nil, fmt.Errorf("cannot encode POST params. %v", err)
}
var request *http.Request
request, err = http.NewRequest("POST", urlStr, buf)
if err != nil {
return nil, err
}
request.Header.Set("Content-Type", mime)
_, data, err := session.sendRequest(request)
if err != nil {
return nil, err
}
if len(data) == 0 {
return nil, fmt.Errorf("empty response from facebook")
}
// facebook may return a query string.
if 'a' <= data[0] && data[0] <= 'z' {
query, err := url.ParseQuery(string(data))
if err != nil {
return nil, err
}
// convert a query to Result.
res := Result{}
for k := range query {
res[k] = query.Get(k)
}
return res, nil
}
res, err := MakeResult(data)
return res, err
}
func (session *Session) sendRequest(request *http.Request) (response *http.Response, data []byte, err error) {
if session.HttpClient == nil {
response, err = http.DefaultClient.Do(request)
} else {
response, err = session.HttpClient.Do(request)
}
if err != nil {
err = fmt.Errorf("cannot reach facebook server. %v", err)
return
}
buf := &bytes.Buffer{}
_, err = io.Copy(buf, response.Body)
response.Body.Close()
if err != nil {
err = fmt.Errorf("cannot read facebook response. %v", err)
}
data = buf.Bytes()
return
}
func (session *Session) isVideoPost(path string, method Method) bool {
return method == POST && regexpIsVideoPost.MatchString(path)
}
func (session *Session) getUrl(name, path string, params Params) string {
offset := 0
if path != "" && path[0] == '/' {
offset = 1
}
buf := &bytes.Buffer{}
buf.WriteString(domainMap[name])
// facebook versioning.
if session.Version == "" {
if Version != "" {
buf.WriteString(Version)
buf.WriteRune('/')
}
} else {
buf.WriteString(session.Version)
buf.WriteRune('/')
}
buf.WriteString(string(path[offset:]))
if params != nil {
buf.WriteRune('?')
params.Encode(buf)
}
return buf.String()
}
func (session *Session) addDebugInfo(res Result, response *http.Response) Result {
if session.Debug() == DEBUG_OFF || res == nil || response == nil {
return res
}
debugInfo := make(map[string]interface{})
// save debug information in result directly.
res.DecodeField("__debug__", &debugInfo)
debugInfo[debugProtoKey] = response.Proto
debugInfo[debugHeaderKey] = response.Header
res["__debug__"] = debugInfo
return res
}
func decodeBase64URLEncodingString(data string) ([]byte, error) {
buf := bytes.NewBufferString(data)
// go's URLEncoding implementation requires base64 padding.
if m := len(data) % 4; m != 0 {
buf.WriteString(strings.Repeat("=", 4-m))
}
reader := base64.NewDecoder(base64.URLEncoding, buf)
output := &bytes.Buffer{}
_, err := io.Copy(output, reader)
if err != nil {
return nil, err
}
return output.Bytes(), nil
}

View File

@@ -1,127 +0,0 @@
// A facebook graph api client in go.
// https://github.com/huandu/facebook/
//
// Copyright 2012 - 2015, Huan Du
// Licensed under the MIT license
// https://github.com/huandu/facebook/blob/master/LICENSE
package facebook
import (
"io"
"net/http"
)
// Holds facebook application information.
type App struct {
// Facebook app id
AppId string
// Facebook app secret
AppSecret string
// Facebook app redirect URI in the app's configuration.
RedirectUri string
// Enable appsecret proof in every API call to facebook.
// Facebook document: https://developers.facebook.com/docs/graph-api/securing-requests
EnableAppsecretProof bool
}
// An interface to send http request.
// This interface is designed to be compatible with type `*http.Client`.
type HttpClient interface {
Do(req *http.Request) (resp *http.Response, err error)
Get(url string) (resp *http.Response, err error)
Post(url string, bodyType string, body io.Reader) (resp *http.Response, err error)
}
// Holds a facebook session with an access token.
// Session should be created by App.Session or App.SessionFromSignedRequest.
type Session struct {
HttpClient HttpClient
Version string // facebook versioning.
accessToken string // facebook access token. can be empty.
app *App
id string
enableAppsecretProof bool // add "appsecret_proof" parameter in every facebook API call.
appsecretProof string // pre-calculated "appsecret_proof" value.
debug DebugMode // using facebook debugging api in every request.
}
// API HTTP method.
// Can be GET, POST or DELETE.
type Method string
// Graph API debug mode.
// See https://developers.facebook.com/docs/graph-api/using-graph-api/v2.3#graphapidebugmode
type DebugMode string
// API params.
//
// For general uses, just use Params as a ordinary map.
//
// For advanced uses, use MakeParams to create Params from any struct.
type Params map[string]interface{}
// Facebook API call result.
type Result map[string]interface{}
// Represents facebook API call result with paging information.
type PagingResult struct {
session *Session
paging pagingData
previous string
next string
}
// Represents facebook batch API call result.
// See https://developers.facebook.com/docs/graph-api/making-multiple-requests/#multiple_methods.
type BatchResult struct {
StatusCode int // HTTP status code.
Header http.Header // HTTP response headers.
Body string // Raw HTTP response body string.
Result Result // Facebook api result parsed from body.
}
// Facebook API error.
type Error struct {
Message string
Type string
Code int
ErrorSubcode int // subcode for authentication related errors.
}
// Binary data.
type binaryData struct {
Filename string // filename used in multipart form writer.
Source io.Reader // file data source.
}
// Binary file.
type binaryFile struct {
Filename string // filename used in multipart form writer.
Path string // path to file. must be readable.
}
// DebugInfo is the debug information returned by facebook when debug mode is enabled.
type DebugInfo struct {
Messages []DebugMessage // debug messages. it can be nil if there is no message.
Header http.Header // all HTTP headers for this response.
Proto string // HTTP protocol name for this response.
// Facebook debug HTTP headers.
FacebookApiVersion string // the actual graph API version provided by facebook-api-version HTTP header.
FacebookDebug string // the X-FB-Debug HTTP header.
FacebookRev string // the x-fb-rev HTTP header.
}
// DebugMessage is one debug message in "__debug__" of graph API response.
type DebugMessage struct {
Type string
Message string
Link string
}

View File

@@ -1,4 +1,4 @@
.PHONY: all test clean build install run stop cover dist dist-battlehouse cleandb
.PHONY: all test clean build install run stop cover dist cleandb travis
GOPATH ?= $(GOPATH:)
GOFLAGS ?= $(GOFLAGS:)
@@ -15,7 +15,11 @@ DIST_RESULTS=$(DIST_ROOT)/results
BENCH=.
TESTS=.
all: build
all: travis
travis:
@echo building for travis
@go build $(GOFLAGS) ./...
build:
@go build $(GOFLAGS) ./...