This repository has been archived by the owner on Jan 4, 2020. It is now read-only.
/
client.go
251 lines (200 loc) · 6.76 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
// Package facebook implements a few functions that basically wrap Go's REST client to work with the Facebook Graph API.
package facebook
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
)
// Client is an object which represents a pathway to the Facebook Graph API.
type Client struct {
appID string
secret string
accessToken AccessToken
// The default Graph API version to use
DefaultVersion GraphAPIVersion
// The HTTP client to use when making API requests
HTTPClient HTTPClient
}
// HTTPMethod is a string which represents an HTTP method (e.g. GET, POST or PUT)
type HTTPMethod string
// GraphAPIVersion is a string which represents the Graph API version to use (e.g. v2.4)
type GraphAPIVersion string
// HTTPClient is the interface which is used to communicate with Facebook. It defaults to http.DefaultClient, but is implemented this way to allow for
// middleware functionality later on.
type HTTPClient interface {
Do(*http.Request) (*http.Response, error)
}
// Some useful constants for building requests
const (
Get HTTPMethod = "GET"
Post = "POST"
Put = "PUT"
Unversioned GraphAPIVersion = ""
Version10 = "v1.0"
Version20 = "v2.0"
Version21 = "v2.1"
Version22 = "v2.2"
Version23 = "v2.3"
Version24 = "v2.4"
Version25 = "v2.5"
Version26 = "v2.6"
Latest = "v2.6"
)
const graphEndpoint string = "https://graph.facebook.com"
// GraphRequest is an HTTP request to the Graph API
type GraphRequest struct {
// The HTTP method used
Method HTTPMethod
// Defaults to the client's DefaultVersion.
Version GraphAPIVersion
// e.g. /me or /starbucks
Path string
// A url.Values representation of desired query string parameters, e.g. width=50&height=50
Query GraphQueryString
// True if the expected content-type of the return is application/json. If this is true, Exec() will try
// to marshal the response as JSON into the target object. Otherwise, it will just set the target object
// as a []byte.
IsJSON bool
client HTTPClient
}
// GraphQueryString is a query string for a GraphRequest
type GraphQueryString url.Values
// An empty Facebook API client with which you can make public requests or set an arbitrary access token.
var BlankClient = New("", "")
// New returns a new *Client. Pass empty strings here if you don't need the object to have your App ID or Secret.
func New(appID string, secret string) (f *Client) {
f = new(Client)
f.appID = appID
f.secret = secret
f.HTTPClient = http.DefaultClient
f.DefaultVersion = Unversioned
return f
}
// SetAccessToken sets the working access token.
func (f *Client) SetAccessToken(at string) {
f.accessToken = AccessToken{token: at}
f.accessToken.Lint(f)
}
// LintAccessToken is an alias for client.AccessToken().Lint().
func (f *Client) LintAccessToken() (err error) {
return f.accessToken.Lint(f)
}
// AccessToken returns the working access token.
func (f *Client) AccessToken() AccessToken {
return f.accessToken
}
// NewGraphRequest builds a new GraphRequest with the passed method, path and query string parameters. If no access token is passed,
// but one is set in the client, it will be appended automatically. Assumes the response will be application/json.
func (f *Client) NewGraphRequest(method HTTPMethod, path string, params GraphQueryString) *GraphRequest {
if params == nil {
params = GraphQueryString{}
}
r := GraphRequest{
Path: path,
Method: method,
Query: params,
IsJSON: true,
Version: f.DefaultVersion,
client: f.HTTPClient,
}
if _, exists := params["access_token"]; !exists && f.accessToken.Empty() == false {
url.Values(r.Query).Add("access_token", f.accessToken.token)
}
return &r
}
// SetVersion sets the Graph API version on the request.
func (r *GraphRequest) SetVersion(v GraphAPIVersion) *GraphRequest {
r.Version = v
return r
}
// Exec executes the given request. Returns a GraphError if the response from Facebook is an error, or just
// a normal error if something goes wrong before that or during unmarshaling.
func (r *GraphRequest) Exec(target interface{}) error {
p := r.Path
if r.Version != Unversioned {
p = "/" + string(r.Version) + "/" + p
}
p = path.Clean(p)
url := url.URL{
Scheme: "https",
Host: "graph.facebook.com",
Path: p,
RawQuery: url.Values(r.Query).Encode(),
}
req, _ := http.NewRequest(string(r.Method), url.String(), nil)
if r.IsJSON {
req.Header.Add("Accept", "application/json")
}
resp, err := r.client.Do(req)
if err != nil {
return fmt.Errorf("error setting up http request: %s", err)
}
buf, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("error reading response body")
}
if resp.StatusCode != 200 {
errorTarget := GraphError{}
err = json.Unmarshal(buf, &errorTarget)
if err != nil {
return fmt.Errorf("couldn't unmarshal response into Graph Error: %s\n\t%s", err, string(buf))
}
return errorTarget
}
if _, ok := target.(*[]byte); ok {
*(target.(*[]byte)) = buf
return nil
} else if r.IsJSON {
err = json.Unmarshal(buf, target)
if err != nil {
return fmt.Errorf("error unmarshaling response into %T: %s\n\n%s", target, err, string(buf))
}
} else {
return fmt.Errorf("invalid target type for non-json response: %T", target)
}
return nil
}
// Get is a wrapper for client.NewGraphRequest(Get, path, params)
func (f *Client) Get(path string, params GraphQueryString) *GraphRequest {
return f.NewGraphRequest(Get, path, params)
}
// Post is a wrapper for client.NewGraphRequest(Post, path, params)
func (f *Client) Post(path string, params GraphQueryString) *GraphRequest {
return f.NewGraphRequest(Post, path, params)
}
// Put is a wrapper for client.NewGraphRequest(Put, path, params)
func (f *Client) Put(path string, params GraphQueryString) *GraphRequest {
return f.NewGraphRequest(Put, path, params)
}
// GetAppAccessToken builds an app access token for the client ID/secret of the client.
func (f *Client) GetAppAccessToken() (string, error) {
var err error
req := f.Get("/oauth/access_token", GraphQueryString{
"client_id": []string{f.appID},
"client_secret": []string{f.secret},
"grant_type": []string{"client_credentials"},
})
target_obj := struct {
Token string `json:"access_token"`
Type string `json:"token_type"`
}{}
err = req.Exec(&target_obj)
if err == nil {
return target_obj.Token, nil
}
fmt.Println(err)
target_raw := []byte{}
err = req.Exec(&target_raw)
if err != nil {
return "", fmt.Errorf("invalid response")
}
vals, _ := url.ParseQuery(string(target_raw))
str, exists := vals["access_token"]
if !exists || str[0] == "" {
return "", fmt.Errorf("access token wasn't in response")
}
return str[0], nil
}