├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── auth ├── auth.go ├── auth_test.go └── user.go ├── commands ├── add.go ├── add_test.go ├── commands.go ├── commands_test.go ├── modify.go ├── modify │ ├── actions.go │ ├── actions_test.go │ ├── add.go │ ├── add_test.go │ ├── archive.go │ ├── archive_test.go │ ├── delete.go │ ├── delete_test.go │ ├── favorite.go │ ├── favorite_test.go │ ├── readd.go │ ├── readd_test.go │ ├── tag_add_test.go │ ├── tag_clear_test.go │ ├── tag_remove_test.go │ ├── tag_rename_test.go │ ├── tag_replace.go │ ├── tag_replace_test.go │ ├── tags_add.go │ ├── tags_clear.go │ ├── tags_remove.go │ ├── tags_rename.go │ ├── unfavorite.go │ └── unfavorite_test.go ├── modify_test.go ├── retrieve.go └── retrieve_test.go ├── doc.go ├── getpocket.go └── utils ├── error.go ├── error_test.go └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | autoreload.sh 2 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 3 | *.o 4 | *.a 5 | *.so 6 | 7 | # Folders 8 | _obj 9 | _test 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | 26 | # Project files 27 | autoreload.sh 28 | cover* 29 | .gvm* 30 | gover* 31 | profile.cov 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.1 4 | - 1.2 5 | - 1.3 6 | - tip 7 | script: make travis 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, github.com/Shaked 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the github.com/Shaked/getpocket nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGETS = auth commands commands\/modify 2 | test: packages 3 | go test -v ./... 4 | 5 | packages: 6 | @-(go get code.google.com/p/go.tools/cmd/cover) 7 | @-(go get golang.org/x/tools/cmd/cover) 8 | go get github.com/axw/gocov/gocov 9 | go get gopkg.in/matm/v1/gocov-html 10 | go get github.com/modocache/gover 11 | go get github.com/mattn/goveralls 12 | 13 | cover: packages 14 | rm -rf *.out 15 | rm -rf cover* 16 | touch cover.json 17 | @for t in $(TARGETS); \ 18 | do \ 19 | v=`echo $$t | sed 's/\//_/g'`; \ 20 | gocov test github.com/Shaked/getpocket/$$t/ -v >> cover_$$v.json; \ 21 | gocov-html cover_$$v.json >> cover_$$v.html; \ 22 | done; 23 | 24 | travis: packages 25 | rm -rf gover.coverprofile 26 | rm -rf profile.cov 27 | @for t in $(TARGETS); \ 28 | do \ 29 | go test -covermode=count -coverprofile=profile.cov github.com/Shaked/getpocket/$$t/; \ 30 | done; 31 | $(HOME)/gopath/bin/gover 32 | $(HOME)/gopath/bin/goveralls -repotoken 4aRDhgifgmEKSiBfuUvQa4whjauFFlkc2 -coverprofile=gover.coverprofile -service travis-ci 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Implementation for [GetPocket](http://www.getpocket.com) API using Go 2 | 3 | 4 | [![Build Status](https://travis-ci.org/Shaked/getpocket.svg?branch=master)](https://travis-ci.org/Shaked/getpocket) 5 | [![Coverage Status](https://img.shields.io/coveralls/Shaked/getpocket.svg)](https://coveralls.io/r/Shaked/getpocket?branch=master) 6 | [![GoDoc](https://godoc.org/github.com/Shaked/getpocket?status.png)](https://godoc.org/github.com/Shaked/getpocket) 7 | 8 | Information & Examples available at [godoc.org](http://godoc.org/github.com/Shaked/getpocket) 9 | 10 | ### Installation 11 | 12 | ``` 13 | go get github.com/Shaked/getpocket 14 | ``` 15 | 16 | ### Requirements 17 | 18 | - HTTPS URLs only 19 | - go version > 1.0 20 | -------------------------------------------------------------------------------- /auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | "net/url" 9 | 10 | "github.com/Shaked/getpocket/utils" 11 | ) 12 | 13 | var ( 14 | mainURL = "https://getpocket.com" 15 | URLs = map[string]string{ 16 | "RequestUrl": "%s/v3/oauth/request", 17 | "RequestTokenUrl": "%s/auth/authorize?request_token=%s&redirect_uri=%s", 18 | "RequestAuthUrl": "%s/v3/oauth/authorize", 19 | } 20 | ) 21 | 22 | //Authentication methods to connect and use GetPocket API 23 | type Authenticator interface { 24 | RequestPermissions(requestToken string, w http.ResponseWriter, r *http.Request) 25 | Connect() (string, *utils.RequestError) 26 | User(requestToken string) (*PocketUser, *utils.RequestError) 27 | } 28 | 29 | //Optional device settings when using GetPocket API 30 | type DeviceControllable interface { 31 | SetForceMobile(forceMobile bool) 32 | } 33 | 34 | type Auth struct { 35 | Authenticator 36 | DeviceControllable 37 | consumerKey string 38 | redirectURI string 39 | mobile bool 40 | request utils.HttpRequest 41 | } 42 | 43 | type authResponseCode struct { 44 | Code string `json:"code"` 45 | } 46 | 47 | func Factory(consumerKey, redirectURI string) (*Auth, *utils.RequestError) { 48 | request := utils.NewRequest() 49 | auth, err := New(consumerKey, redirectURI, request) 50 | return auth, err 51 | } 52 | 53 | //Creates new getpocket auth instance 54 | //consumerKey is available at http://getpocket.com/developers, redirectURI must include full URL 55 | func New(consumerKey, redirectURI string, request utils.HttpRequest) (*Auth, *utils.RequestError) { 56 | p, err := url.Parse(redirectURI) 57 | if nil != err { 58 | return nil, utils.NewRequestError(http.StatusInternalServerError, err) 59 | } 60 | 61 | if "https" != p.Scheme { 62 | return nil, utils.NewRequestError(http.StatusInternalServerError, errors.New( 63 | fmt.Sprintf("Invalid redirectURI, HTTPS is required. %s", p.RawQuery))) 64 | } 65 | 66 | return &Auth{ 67 | consumerKey: consumerKey, 68 | redirectURI: redirectURI, 69 | request: request, 70 | }, nil 71 | } 72 | 73 | //Connect to GP API using the consumerKey 74 | func (a *Auth) Connect() (string, *utils.RequestError) { 75 | code, err := a.code() 76 | if nil != err { 77 | return "", utils.NewRequestError(http.StatusInternalServerError, err) 78 | } 79 | return code, nil 80 | } 81 | 82 | //Request GP API for permissions 83 | func (a *Auth) RequestPermissions(requestToken string, w http.ResponseWriter, r *http.Request) { 84 | //not checking for error as it is being checked when calling New() 85 | u, _ := url.Parse(a.redirectURI) 86 | q := u.Query() 87 | q.Add("requestToken", requestToken) 88 | u.RawQuery = q.Encode() 89 | redirectURL := fmt.Sprintf( 90 | URLs["RequestTokenUrl"], 91 | mainURL, 92 | requestToken, 93 | url.QueryEscape(u.String()), 94 | ) 95 | 96 | if a.mobile { 97 | redirectURL += "&mobile=1" 98 | } 99 | 100 | http.Redirect(w, r, redirectURL, http.StatusTemporaryRedirect) 101 | } 102 | 103 | //Make final authentication and retrieve user details (username, access_token) 104 | func (a *Auth) User(requestToken string) (*PocketUser, *utils.RequestError) { 105 | values := make(url.Values) 106 | values.Set("consumer_key", a.consumerKey) 107 | values.Set("code", requestToken) 108 | body, err := a.request.Post(fmt.Sprintf(URLs["RequestAuthUrl"], mainURL), values) 109 | 110 | if nil != err { 111 | return nil, err 112 | } 113 | 114 | user := &PocketUser{} 115 | e := json.Unmarshal(body, user) 116 | if nil != e { 117 | return nil, utils.NewRequestError(http.StatusInternalServerError, e) 118 | } 119 | return user, nil 120 | } 121 | 122 | //@see http://getpocket.com/developer/docs/authentication 123 | func (a *Auth) SetForceMobile(forceMobile bool) { 124 | a.mobile = forceMobile 125 | } 126 | 127 | // private methods 128 | func (a *Auth) code() (string, *utils.RequestError) { 129 | values := make(url.Values) 130 | values.Set("consumer_key", a.consumerKey) 131 | values.Set("redirect_uri", a.redirectURI) 132 | requestURL := fmt.Sprintf(URLs["RequestUrl"], mainURL) 133 | body, err := a.request.Post(requestURL, values) 134 | if nil != err { 135 | return "", utils.NewRequestError(http.StatusInternalServerError, err) 136 | } 137 | 138 | res := &authResponseCode{} 139 | 140 | e := json.Unmarshal(body, res) 141 | if nil != e { 142 | return "", utils.NewRequestError(http.StatusInternalServerError, e) 143 | } 144 | return res.Code, nil 145 | } 146 | -------------------------------------------------------------------------------- /auth/auth_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "net/http/httptest" 9 | "strconv" 10 | "testing" 11 | 12 | "github.com/Shaked/getpocket/utils" 13 | ) 14 | 15 | var request = utils.NewRequest() 16 | 17 | func TestNew(t *testing.T) { 18 | _, _ = Factory("consumerKey", "http://www.c.eom") 19 | _, e := New("consumerKey", ":ww.s.com", request) 20 | if http.StatusInternalServerError != e.ErrorCode() { 21 | t.Error(e) 22 | } 23 | 24 | _, e = New("consumerKey", "http://someurl.org", request) 25 | if http.StatusInternalServerError != e.ErrorCode() && "HTTPS is required. http://someurl.org" != e.Error() { 26 | t.Error(e) 27 | } 28 | 29 | _, e = New("consumerKey", "https://someurl.org", request) 30 | if nil != e { 31 | t.Error(e) 32 | } 33 | } 34 | 35 | func TestConnectSuccess(t *testing.T) { 36 | pocketServer := httptest.NewServer( 37 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 38 | d := map[string]string{"Code": "123123123"} 39 | res, _ := json.Marshal(d) 40 | fmt.Fprint(w, string(res)) 41 | })) 42 | 43 | ts := httptest.NewTLSServer( 44 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) 45 | 46 | defer pocketServer.Close() 47 | defer ts.Close() 48 | mainURL = pocketServer.URL 49 | 50 | a, e := New("consumerKey", ts.URL, request) 51 | if nil != e { 52 | t.Errorf("New function failed, error: %s", e) 53 | } 54 | 55 | c, e := a.Connect() 56 | if nil != e { 57 | t.Errorf("Connect method failed, error: %s", e) 58 | } 59 | 60 | if "123123123" != c { 61 | t.Errorf("Wrong API code returned: %s", c) 62 | } 63 | } 64 | func TestConnectErrors(t *testing.T) { 65 | ts := httptest.NewTLSServer( 66 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) 67 | defer ts.Close() 68 | 69 | requests := []struct { 70 | mainURL string 71 | result string 72 | errorMsg string 73 | headers map[string]string 74 | }{ 75 | { 76 | mainURL: "%s", 77 | result: "", 78 | errorMsg: "invalid json", 79 | headers: map[string]string{}, 80 | }, 81 | { 82 | mainURL: ":h", 83 | result: "result", 84 | errorMsg: "invalid scheme", 85 | headers: map[string]string{}, 86 | }, 87 | { 88 | mainURL: "http://some.%s", 89 | result: "result", 90 | errorMsg: "invalid response", 91 | headers: map[string]string{}, 92 | }, 93 | { 94 | mainURL: "%s", 95 | result: "", 96 | errorMsg: "invalid header errors", 97 | headers: map[string]string{ 98 | "StatusCode": strconv.Itoa(http.StatusInternalServerError), 99 | "X-Error-Code": "132", 100 | "X-Error": "Text Error", 101 | }, 102 | }, 103 | } 104 | 105 | type testResult struct { 106 | success bool 107 | msg string 108 | } 109 | 110 | ch := make(chan *testResult, len(requests)) 111 | defer close(ch) 112 | go (func(chan *testResult) { 113 | for _, r := range requests { 114 | tr := &testResult{true, ""} 115 | a, _ := New("consumerKey", ts.URL, request) 116 | handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 117 | var sc int 118 | s, ok := r.headers["StatusCode"] 119 | if ok { 120 | sc, _ = strconv.Atoi(s) 121 | delete(r.headers, "StatusCode") 122 | http.Error(w, "error", sc) 123 | } 124 | 125 | for hk, hv := range r.headers { 126 | w.Header().Set(hk, hv) 127 | } 128 | 129 | if "" != r.result { 130 | fmt.Fprint(w, r.result) 131 | } 132 | }) 133 | pocketServer := httptest.NewServer(handler) 134 | defer pocketServer.Close() 135 | mainURL = fmt.Sprintf(r.mainURL, pocketServer.URL) 136 | c, e := a.Connect() 137 | if nil == e { 138 | tr.success = false 139 | tr.msg = fmt.Sprintf("%s, %s", r.errorMsg, c) 140 | } 141 | ch <- tr 142 | } 143 | })(ch) 144 | 145 | for i := 0; i < len(requests); i++ { 146 | tr := <-ch 147 | if !tr.success { 148 | t.Error(tr.msg) 149 | } 150 | } 151 | } 152 | 153 | func TestUserSuccess(t *testing.T) { 154 | ts := httptest.NewTLSServer( 155 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) 156 | defer ts.Close() 157 | 158 | pocketServer := httptest.NewServer( 159 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 160 | d := map[string]string{"username": "shaked", "access_token": "accessToken"} 161 | res, _ := json.Marshal(d) 162 | fmt.Fprint(w, string(res)) 163 | })) 164 | 165 | a, _ := New("consumerKey", ts.URL, request) 166 | mainURL = pocketServer.URL 167 | user, err := a.User("requestToken") 168 | if nil != err { 169 | t.Errorf("invaild user, error: %s", err) 170 | } 171 | 172 | if "accessToken" != user.AccessToken() || "shaked" != user.Username() { 173 | t.Errorf("invaild user, user: %#v", user) 174 | } 175 | } 176 | 177 | func TestUserErrors(t *testing.T) { 178 | ts := httptest.NewTLSServer( 179 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) 180 | defer ts.Close() 181 | 182 | var toClose bool 183 | handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 184 | if toClose { 185 | w.Write([]byte("|")) 186 | v, _ := w.(http.Hijacker) 187 | conn, _, _ := v.Hijack() 188 | conn.Close() 189 | } 190 | }) 191 | 192 | pocketServer := httptest.NewServer(handler) 193 | defer pocketServer.Close() 194 | 195 | a, _ := New("consumerKey", ts.URL, request) 196 | mainURL = pocketServer.URL 197 | r, err := a.User("requestToken") 198 | if nil == err { 199 | t.Errorf("json should be invalid, error %s", r) 200 | } 201 | 202 | toClose = true 203 | r, err = a.User("requestToken") 204 | if nil == err { 205 | t.Errorf("request should be closed, error %s", r) 206 | } 207 | 208 | } 209 | 210 | func TestRequestPermissions(t *testing.T) { 211 | pocketServer := httptest.NewServer( 212 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) 213 | 214 | ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 215 | a, _ := New("consumerKey", r.URL.Query().Get("redirectURI"), request) 216 | a.SetForceMobile(true) 217 | a.RequestPermissions("SomeRequestToken", w, r) 218 | })) 219 | 220 | defer pocketServer.Close() 221 | defer ts.Close() 222 | mainURL = pocketServer.URL 223 | 224 | //make sure redirect works 225 | client := newClient() 226 | client.CheckRedirect = func(req *http.Request, via []*http.Request) error { 227 | requestToken := req.URL.Query().Get("request_token") 228 | if "SomeRequestToken" != requestToken { 229 | t.Errorf("Wrong request_token, actual: %s", requestToken) 230 | } 231 | return nil 232 | } 233 | 234 | //call request permission url 235 | req, e := client.Get(ts.URL + "?redirectURI=" + ts.URL) 236 | if nil != e { 237 | t.Fatalf("Get request failed, error: %s", e) 238 | } 239 | defer req.Body.Close() 240 | 241 | } 242 | 243 | func newClient() *http.Client { 244 | tr := &http.Transport{ 245 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 246 | } 247 | return &http.Client{Transport: tr} 248 | } 249 | -------------------------------------------------------------------------------- /auth/user.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | type Authenticated interface { 4 | AccessToken() string 5 | } 6 | 7 | type User interface { 8 | Username() string 9 | } 10 | 11 | type PocketUser struct { 12 | PAccessToken string `json:"access_token"` 13 | PUsername string `json:"username"` 14 | } 15 | 16 | func NewUser() *PocketUser { 17 | return &PocketUser{} 18 | } 19 | 20 | func (u *PocketUser) SetAccessToken(accessToken string) *PocketUser { 21 | u.PAccessToken = accessToken 22 | return u 23 | } 24 | 25 | func (u *PocketUser) SetUsername(username string) *PocketUser { 26 | u.PUsername = username 27 | return u 28 | } 29 | 30 | func (u *PocketUser) AccessToken() string { 31 | return u.PAccessToken 32 | } 33 | 34 | func (u *PocketUser) Username() string { 35 | return u.PUsername 36 | } 37 | -------------------------------------------------------------------------------- /commands/add.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | 7 | "github.com/Shaked/getpocket/auth" 8 | "github.com/Shaked/getpocket/utils" 9 | ) 10 | 11 | //@see http://getpocket.com/developer/docs/v3/add 12 | type Add struct { 13 | command 14 | 15 | URL string 16 | title string 17 | tags string 18 | tweet_id string 19 | } 20 | 21 | type AddResponse struct { 22 | Item Item 23 | Status int 24 | } 25 | 26 | func NewAdd(consumerKey string, request utils.HttpRequest, targetURL string) *Add { 27 | a := &Add{ 28 | URL: targetURL, 29 | } 30 | a.SetConsumerKey(consumerKey) 31 | a.SetRequest(request) 32 | 33 | return a 34 | } 35 | 36 | // This can be included for cases where an item does not have a title, which is typical for image or PDF URLs. 37 | // If Pocket detects a title from the content of the page, this parameter will be ignored. 38 | func (a *Add) SetTitle(title string) *Add { 39 | a.title = title 40 | return a 41 | } 42 | 43 | func (a *Add) SetTags(tags string) *Add { 44 | a.tags = tags 45 | return a 46 | } 47 | 48 | func (a *Add) SetTweetID(tweet_id string) *Add { 49 | a.tweet_id = tweet_id 50 | return a 51 | } 52 | 53 | func (a *Add) Exec(user auth.Authenticated) (*AddResponse, error) { 54 | u := url.Values{} 55 | 56 | u.Add("url", a.URL) 57 | u.Add("consumer_key", a.consumerKey) 58 | u.Add("access_token", user.AccessToken()) 59 | 60 | if "" != a.title { 61 | u.Add("title", a.title) 62 | } 63 | 64 | if "" != a.tags { 65 | u.Add("tags", a.tags) 66 | } 67 | 68 | if "" != a.tweet_id { 69 | u.Add("tweet_id", a.tweet_id) 70 | } 71 | 72 | body, err := a.request.Post(URLs["Add"], u) 73 | if nil != err { 74 | return nil, err 75 | } 76 | 77 | resp := &AddResponse{} 78 | e := json.Unmarshal(fixJSONArrayToObject(body), resp) 79 | if nil != e { 80 | return nil, e 81 | } 82 | return resp, nil 83 | } 84 | -------------------------------------------------------------------------------- /commands/add_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/Shaked/getpocket/auth" 8 | "github.com/Shaked/getpocket/utils" 9 | ) 10 | 11 | func TestNewAdd(t *testing.T) { 12 | r := &request{ret: "{}"} 13 | a := NewAdd("consumer-key", r, "target_url") 14 | if "target_url" != a.URL { 15 | t.Fail() 16 | } 17 | 18 | a.SetTitle("title").SetTags("tag1,tags2").SetTweetID("1234") 19 | user := auth.NewUser().SetAccessToken("access_token").SetUsername("username") 20 | _, err := a.Exec(user) 21 | if nil != err { 22 | t.Errorf("error %s", err) 23 | } 24 | 25 | r = &request{err: utils.NewRequestError(1, errors.New("just an error"))} 26 | a = NewAdd("consumer-key", r, "target_url") 27 | _, err = a.Exec(user) 28 | if nil == err { 29 | t.Fail() 30 | } 31 | 32 | r = &request{ret: "\n"} 33 | a = NewAdd("consumer-key", r, "target_url") 34 | _, err = a.Exec(user) 35 | if nil == err { 36 | t.Fail() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /commands/commands.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/Shaked/getpocket/utils" 7 | ) 8 | 9 | var ( 10 | URLs = map[string]string{ 11 | "Add": "https://getpocket.com/v3/add", 12 | "Retrieve": "https://getpocket.com/v3/get", 13 | "Modify": "https://getpocket.com/v3/send", 14 | } 15 | ) 16 | 17 | type Item struct { 18 | Id string `json:"item_id"` 19 | NormalURL string `json:"normal_url"` 20 | ResolvedId string `json:"resolved_id"` 21 | ResolvedURL string `json:"resolved_url"` 22 | DomainId string `json:"domain_id"` 23 | OriginDomainId string `json:"origin_domain_id"` 24 | ResponseCode string `json:"response_code"` 25 | MimeType string `json:"mime_type"` 26 | ContentLength string `json:"content_length"` 27 | Encoding string `json:"encoding"` 28 | DateResolved string `json:"date_resolved"` 29 | DatePublished string `json:"date_published"` 30 | Title string `json:"title"` 31 | Excerpt string `json:"excerpt"` 32 | WordCount string `json:"word_count"` 33 | HasImage string `json:"has_image"` 34 | HasVideo string `json:"has_video"` 35 | IsIndex string `json:"is_index"` 36 | Authors map[string]ItemAuthor `json:"authors"` 37 | Images map[string]ItemImage `json:"images"` 38 | Videos map[string]ItemVideo `json:"videos"` 39 | } 40 | 41 | type ItemImage struct { 42 | Id string `json:"item_id"` 43 | ImageId string `json:"image_id"` 44 | Src string `json:"src"` 45 | Width string `json:"width"` 46 | Height string `json:"height"` 47 | Credit string `json:"credit"` 48 | } 49 | 50 | type ItemVideo struct { 51 | Id string `json:"item_id"` 52 | VideoId string `json:"video_id"` 53 | Src string `json:"src"` 54 | Width string `json:"width"` 55 | Height string `json:"height"` 56 | Type string `json:"credit"` 57 | Vid string `json:"vid"` 58 | Length string `json:"length"` 59 | } 60 | 61 | type ItemAuthor struct { 62 | Id string `json:"author_id"` 63 | Name string `json:"name"` 64 | URL string `json:"url"` 65 | } 66 | 67 | type ItemTag struct { 68 | Id string `json:"item_id"` 69 | Tag string `json:"tag"` 70 | } 71 | 72 | type command struct { 73 | consumerKey string 74 | request utils.HttpRequest 75 | } 76 | 77 | func (c *command) SetConsumerKey(consumerKey string) { 78 | c.consumerKey = consumerKey 79 | } 80 | 81 | func (c *command) SetRequest(request utils.HttpRequest) { 82 | if nil == request { 83 | request = utils.NewRequest() 84 | } 85 | c.request = request 86 | } 87 | 88 | //get pocket returns an empty array instead of an empty object. 89 | func fixJSONArrayToObject(body []byte) []byte { 90 | newStr := string(body) 91 | newStr = strings.Replace(newStr, `"tags":[]`, `"tags":{}`, -1) 92 | newStr = strings.Replace(newStr, `"videos":[]`, `"videos":{}`, -1) 93 | newStr = strings.Replace(newStr, `"images":[]`, `"images":{}`, -1) 94 | newStr = strings.Replace(newStr, `"authors":[]`, `"authors":{}`, -1) 95 | newStr = strings.Replace(newStr, `"list":[]`, `"list":{}`, -1) 96 | return []byte(newStr) 97 | } 98 | -------------------------------------------------------------------------------- /commands/commands_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "net/url" 5 | "testing" 6 | 7 | "github.com/Shaked/getpocket/utils" 8 | ) 9 | 10 | type request struct { 11 | ret string 12 | err *utils.RequestError 13 | } 14 | 15 | func (r *request) Post(url string, values url.Values) ([]byte, *utils.RequestError) { 16 | return []byte(r.ret), r.err 17 | } 18 | 19 | func TestfixJSONArrayToObject(t *testing.T) { 20 | // This 21 | apiResult := []byte(`{"item_id":"1", "videos":[],"authors":[],"images":[]}`) 22 | expected := []byte(`{"item_id":"1", "videos":{},"authors":{},"images":{}}`) 23 | actual := fixJSONArrayToObject(apiResult) 24 | if string(expected) != string(actual) { 25 | t.Errorf("Actual value is worng %s", string(actual)) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /commands/modify.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | 7 | "github.com/Shaked/getpocket/auth" 8 | "github.com/Shaked/getpocket/commands/modify" 9 | "github.com/Shaked/getpocket/utils" 10 | ) 11 | 12 | //@see http://getpocket.com/developer/docs/v3/add 13 | type Modify struct { 14 | command 15 | 16 | actions []modify.Action 17 | } 18 | 19 | type ModifyResponse struct { 20 | ActionResults []bool `json:"action_results"` 21 | Status int `json:"status"` 22 | } 23 | 24 | func NewModify(consumerKey string, request utils.HttpRequest, actions []modify.Action) *Modify { 25 | m := &Modify{actions: actions} 26 | m.SetConsumerKey(consumerKey) 27 | m.SetRequest(request) 28 | return m 29 | } 30 | 31 | func (c *Modify) Exec(user auth.Authenticated) (*ModifyResponse, error) { 32 | u := url.Values{} 33 | u.Add("consumer_key", c.consumerKey) 34 | u.Add("access_token", user.AccessToken()) 35 | 36 | actions, e := json.Marshal(c.actions) 37 | if nil != e { 38 | return nil, e 39 | } 40 | u.Add("actions", string(actions)) 41 | body, err := c.request.Post(URLs["Modify"], u) 42 | if nil != err { 43 | return nil, err 44 | } 45 | 46 | resp := &ModifyResponse{} 47 | e = json.Unmarshal(fixJSONArrayToObject(body), resp) 48 | if nil != e { 49 | return nil, e 50 | } 51 | return resp, nil 52 | } 53 | -------------------------------------------------------------------------------- /commands/modify/actions.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | ) 7 | 8 | type Action interface { 9 | json.Marshaler 10 | } 11 | 12 | type ProtocolTags struct { 13 | Tags string `json:"tags,omitempty"` 14 | } 15 | 16 | type Protocol struct { 17 | Id int `json:"item_id"` 18 | Time string `json:"timestamp,omitempty"` 19 | } 20 | 21 | func (a *ProtocolTags) SetTags(tags []string) *ProtocolTags { 22 | a.Tags = strings.Join(tags, ",") 23 | return a 24 | } 25 | 26 | func (a *Protocol) SetTS(ts string) *Protocol { 27 | a.Time = ts 28 | return a 29 | } 30 | -------------------------------------------------------------------------------- /commands/modify/actions_test.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func ValidateJSONs(t *testing.T, a Action, m interface{}) { 9 | j, e := json.Marshal(a) 10 | if nil != e { 11 | t.Error(e) 12 | } 13 | expectedJSON, _ := json.Marshal(m) 14 | if string(j) != string(expectedJSON) { 15 | t.Errorf("Expected %s is not the same as actual %s", expectedJSON, j) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /commands/modify/add.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "encoding/json" 4 | 5 | type Add struct { 6 | *Protocol 7 | *ProtocolTags 8 | RefId int 9 | Title string 10 | URL string 11 | } 12 | 13 | func FactoryAdd(id int) *Add { 14 | p := &Protocol{Id: id} 15 | return NewAdd(p) 16 | } 17 | 18 | func NewAdd(p *Protocol) *Add { 19 | pt := &ProtocolTags{} 20 | return &Add{Protocol: p, ProtocolTags: pt} 21 | } 22 | 23 | func (a *Add) SetRefId(refId int) *Add { 24 | a.RefId = refId 25 | return a 26 | } 27 | 28 | func (a *Add) SetTitle(title string) *Add { 29 | a.Title = title 30 | return a 31 | } 32 | 33 | func (a *Add) SetURL(url string) *Add { 34 | a.URL = url 35 | return a 36 | } 37 | 38 | func (a *Add) MarshalJSON() ([]byte, error) { 39 | m := struct { 40 | Action string `json:"action"` 41 | *Protocol 42 | *ProtocolTags 43 | RefId int `json:"ref_id,omitempty"` 44 | Title string `json:"title,omitempty"` 45 | URL string `json:"url,omitempty"` 46 | }{ 47 | Action: "add", 48 | Protocol: a.Protocol, 49 | ProtocolTags: a.ProtocolTags, 50 | } 51 | 52 | if 0 < a.RefId { 53 | m.RefId = a.RefId 54 | } 55 | 56 | if "" != a.Title { 57 | m.Title = a.Title 58 | } 59 | 60 | if "" != a.URL { 61 | m.URL = a.URL 62 | } 63 | 64 | return json.Marshal(m) 65 | } 66 | -------------------------------------------------------------------------------- /commands/modify/add_test.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "testing" 4 | 5 | func TestAddMarshalJSON(t *testing.T) { 6 | a := FactoryAdd(1) 7 | m1 := struct { 8 | Action string `json:"action"` 9 | Id int `json:"item_id"` 10 | }{ 11 | Action: "add", 12 | Id: 1, 13 | } 14 | ValidateJSONs(t, a, m1) 15 | 16 | now := "123123123" 17 | a.SetRefId(2).SetTitle("title").SetURL("url") 18 | a.SetTags([]string{"tag1", "tag2"}) 19 | a.SetTS(now) 20 | m2 := struct { 21 | Action string `json:"action"` 22 | Id int `json:"item_id"` 23 | Time string `json:"timestamp"` 24 | Tags string `json:"tags"` 25 | RefId int `json:"ref_id"` 26 | Title string `json:"title"` 27 | URL string `json:"url"` 28 | }{ 29 | Action: "add", 30 | Id: 1, 31 | Time: now, 32 | Tags: "tag1,tag2", 33 | RefId: a.RefId, 34 | Title: a.Title, 35 | URL: a.URL, 36 | } 37 | 38 | ValidateJSONs(t, a, m2) 39 | } 40 | -------------------------------------------------------------------------------- /commands/modify/archive.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "encoding/json" 4 | 5 | type Archive struct { 6 | *Protocol 7 | } 8 | 9 | func FactoryArchive(id int) *Archive { 10 | p := &Protocol{Id: id} 11 | return NewArchive(p) 12 | } 13 | 14 | func NewArchive(p *Protocol) *Archive { 15 | return &Archive{Protocol: p} 16 | } 17 | 18 | func (a *Archive) MarshalJSON() ([]byte, error) { 19 | m := struct { 20 | Action string `json:"action"` 21 | *Protocol 22 | }{ 23 | Action: "archive", 24 | Protocol: a.Protocol, 25 | } 26 | 27 | return json.Marshal(m) 28 | } 29 | -------------------------------------------------------------------------------- /commands/modify/archive_test.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "testing" 4 | 5 | func TestArchiveMarshalJSON(t *testing.T) { 6 | a := FactoryArchive(1) 7 | m1 := struct { 8 | Action string `json:"action"` 9 | Id int `json:"item_id"` 10 | }{ 11 | Action: "archive", 12 | Id: 1, 13 | } 14 | 15 | ValidateJSONs(t, a, m1) 16 | 17 | a.SetTS("time") 18 | m2 := struct { 19 | Action string `json:"action"` 20 | Id int `json:"item_id"` 21 | Time string `json:"timestamp"` 22 | }{ 23 | Action: "archive", 24 | Id: 1, 25 | Time: "time", 26 | } 27 | 28 | ValidateJSONs(t, a, m2) 29 | } 30 | -------------------------------------------------------------------------------- /commands/modify/delete.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "encoding/json" 4 | 5 | type Delete struct { 6 | *Protocol 7 | } 8 | 9 | func FactoryDelete(id int) *Delete { 10 | p := &Protocol{Id: id} 11 | return NewDelete(p) 12 | } 13 | 14 | func NewDelete(p *Protocol) *Delete { 15 | return &Delete{Protocol: p} 16 | } 17 | 18 | func (a *Delete) MarshalJSON() ([]byte, error) { 19 | m := struct { 20 | Action string `json:"action"` 21 | *Protocol 22 | }{ 23 | Action: "delete", 24 | Protocol: a.Protocol, 25 | } 26 | 27 | return json.Marshal(m) 28 | } 29 | -------------------------------------------------------------------------------- /commands/modify/delete_test.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "testing" 4 | 5 | func TestDeleteMarshalJSON(t *testing.T) { 6 | a := FactoryDelete(1) 7 | m1 := struct { 8 | Action string `json:"action"` 9 | Id int `json:"item_id"` 10 | }{ 11 | Action: "delete", 12 | Id: 1, 13 | } 14 | 15 | ValidateJSONs(t, a, m1) 16 | 17 | a.SetTS("time") 18 | m2 := struct { 19 | Action string `json:"action"` 20 | Id int `json:"item_id"` 21 | Time string `json:"timestamp"` 22 | }{ 23 | Action: "delete", 24 | Id: 1, 25 | Time: "time", 26 | } 27 | 28 | ValidateJSONs(t, a, m2) 29 | } 30 | -------------------------------------------------------------------------------- /commands/modify/favorite.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "encoding/json" 4 | 5 | type Favorite struct { 6 | *Protocol 7 | } 8 | 9 | func FactoryFavorite(id int) *Favorite { 10 | p := &Protocol{Id: id} 11 | return NewFavorite(p) 12 | } 13 | 14 | func NewFavorite(p *Protocol) *Favorite { 15 | return &Favorite{Protocol: p} 16 | } 17 | 18 | func (a *Favorite) MarshalJSON() ([]byte, error) { 19 | m := struct { 20 | Action string `json:"action"` 21 | *Protocol 22 | }{ 23 | Action: "favorite", 24 | Protocol: a.Protocol, 25 | } 26 | 27 | return json.Marshal(m) 28 | } 29 | -------------------------------------------------------------------------------- /commands/modify/favorite_test.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "testing" 4 | 5 | func TestFavoriteMarshalJSON(t *testing.T) { 6 | a := FactoryFavorite(1) 7 | m1 := struct { 8 | Action string `json:"action"` 9 | Id int `json:"item_id"` 10 | }{ 11 | Action: "favorite", 12 | Id: 1, 13 | } 14 | 15 | ValidateJSONs(t, a, m1) 16 | 17 | a.SetTS("time") 18 | m2 := struct { 19 | Action string `json:"action"` 20 | Id int `json:"item_id"` 21 | Time string `json:"timestamp"` 22 | }{ 23 | Action: "favorite", 24 | Id: 1, 25 | Time: "time", 26 | } 27 | 28 | ValidateJSONs(t, a, m2) 29 | } 30 | -------------------------------------------------------------------------------- /commands/modify/readd.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "encoding/json" 4 | 5 | type Readd struct { 6 | *Protocol 7 | } 8 | 9 | func FactoryReadd(id int) *Readd { 10 | p := &Protocol{Id: id} 11 | return NewReadd(p) 12 | } 13 | 14 | func NewReadd(p *Protocol) *Readd { 15 | return &Readd{Protocol: p} 16 | } 17 | 18 | func (a *Readd) MarshalJSON() ([]byte, error) { 19 | m := struct { 20 | Action string `json:"action"` 21 | *Protocol 22 | }{ 23 | Action: "readd", 24 | Protocol: a.Protocol, 25 | } 26 | 27 | return json.Marshal(m) 28 | } 29 | -------------------------------------------------------------------------------- /commands/modify/readd_test.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "testing" 4 | 5 | func TestReaddMarshalJSON(t *testing.T) { 6 | a := FactoryReadd(1) 7 | m1 := struct { 8 | Action string `json:"action"` 9 | Id int `json:"item_id"` 10 | }{ 11 | Action: "readd", 12 | Id: 1, 13 | } 14 | 15 | ValidateJSONs(t, a, m1) 16 | 17 | a.SetTS("time") 18 | m2 := struct { 19 | Action string `json:"action"` 20 | Id int `json:"item_id"` 21 | Time string `json:"timestamp"` 22 | }{ 23 | Action: "readd", 24 | Id: 1, 25 | Time: "time", 26 | } 27 | 28 | ValidateJSONs(t, a, m2) 29 | } 30 | -------------------------------------------------------------------------------- /commands/modify/tag_add_test.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "testing" 4 | 5 | func TestTagsAddMarshalJSON(t *testing.T) { 6 | a := FactoryTagsAdd(1, []string{`tag1`, `tag2`}) 7 | m1 := struct { 8 | Action string `json:"action"` 9 | Id int `json:"item_id"` 10 | Tags string `json:"tags"` 11 | }{ 12 | Action: "tags_add", 13 | Id: 1, 14 | Tags: `tag1,tag2`, 15 | } 16 | 17 | ValidateJSONs(t, a, m1) 18 | 19 | a.SetTS("time") 20 | m2 := struct { 21 | Action string `json:"action"` 22 | Id int `json:"item_id"` 23 | Time string `json:"timestamp"` 24 | Tags string `json:"tags"` 25 | }{ 26 | Action: "tags_add", 27 | Id: 1, 28 | Tags: `tag1,tag2`, 29 | Time: "time", 30 | } 31 | 32 | ValidateJSONs(t, a, m2) 33 | } 34 | -------------------------------------------------------------------------------- /commands/modify/tag_clear_test.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "testing" 4 | 5 | func TestTagsClearMarshalJSON(t *testing.T) { 6 | a := FactoryTagsClear(1) 7 | m1 := struct { 8 | Action string `json:"action"` 9 | Id int `json:"item_id"` 10 | }{ 11 | Action: "tags_clear", 12 | Id: 1, 13 | } 14 | 15 | ValidateJSONs(t, a, m1) 16 | 17 | a.SetTS("time") 18 | m2 := struct { 19 | Action string `json:"action"` 20 | Id int `json:"item_id"` 21 | Time string `json:"timestamp"` 22 | }{ 23 | Action: "tags_clear", 24 | Id: 1, 25 | Time: "time", 26 | } 27 | 28 | ValidateJSONs(t, a, m2) 29 | } 30 | -------------------------------------------------------------------------------- /commands/modify/tag_remove_test.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "testing" 4 | 5 | func TestTagsRemoveMarshalJSON(t *testing.T) { 6 | a := FactoryTagsRemove(1, []string{`tag1`, `tag2`}) 7 | m1 := struct { 8 | Action string `json:"action"` 9 | Id int `json:"item_id"` 10 | Tags string `json:"tags"` 11 | }{ 12 | Action: "tags_remove", 13 | Id: 1, 14 | Tags: `tag1,tag2`, 15 | } 16 | 17 | ValidateJSONs(t, a, m1) 18 | 19 | a.SetTS("time") 20 | m2 := struct { 21 | Action string `json:"action"` 22 | Id int `json:"item_id"` 23 | Time string `json:"timestamp"` 24 | Tags string `json:"tags"` 25 | }{ 26 | Action: "tags_remove", 27 | Id: 1, 28 | Tags: `tag1,tag2`, 29 | Time: "time", 30 | } 31 | 32 | ValidateJSONs(t, a, m2) 33 | } 34 | -------------------------------------------------------------------------------- /commands/modify/tag_rename_test.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "testing" 4 | 5 | func TestTagsRenameMarshalJSON(t *testing.T) { 6 | a := FactoryTagsRename(1, `tag1`, `tag2`) 7 | m1 := struct { 8 | Action string `json:"action"` 9 | Id int `json:"item_id"` 10 | OldTag string `json:"old_tag"` 11 | NewTag string `json:"new_tag"` 12 | }{ 13 | Action: "tags_rename", 14 | Id: 1, 15 | OldTag: `tag1`, 16 | NewTag: `tag2`, 17 | } 18 | 19 | ValidateJSONs(t, a, m1) 20 | 21 | a.SetTS("time") 22 | m2 := struct { 23 | Action string `json:"action"` 24 | Id int `json:"item_id"` 25 | Time string `json:"timestamp"` 26 | OldTag string `json:"old_tag"` 27 | NewTag string `json:"new_tag"` 28 | }{ 29 | Action: "tags_rename", 30 | Id: 1, 31 | OldTag: `tag1`, 32 | NewTag: `tag2`, 33 | Time: "time", 34 | } 35 | 36 | ValidateJSONs(t, a, m2) 37 | } 38 | -------------------------------------------------------------------------------- /commands/modify/tag_replace.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "encoding/json" 4 | 5 | type TagsReplace struct { 6 | *Protocol 7 | *ProtocolTags 8 | } 9 | 10 | func FactoryTagsReplace(id int, tags []string) *TagsReplace { 11 | p := &Protocol{Id: id} 12 | pt := &ProtocolTags{} 13 | pt.SetTags(tags) 14 | return NewTagsReplace(p, pt) 15 | } 16 | 17 | func NewTagsReplace(p *Protocol, pt *ProtocolTags) *TagsReplace { 18 | 19 | return &TagsReplace{Protocol: p, ProtocolTags: pt} 20 | } 21 | 22 | func (a *TagsReplace) MarshalJSON() ([]byte, error) { 23 | m := struct { 24 | Action string `json:"action"` 25 | *Protocol 26 | *ProtocolTags 27 | }{ 28 | Action: "tags_replace", 29 | Protocol: a.Protocol, 30 | ProtocolTags: a.ProtocolTags, 31 | } 32 | 33 | return json.Marshal(m) 34 | } 35 | -------------------------------------------------------------------------------- /commands/modify/tag_replace_test.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "testing" 4 | 5 | func TestTagsReplaceMarshalJSON(t *testing.T) { 6 | a := FactoryTagsReplace(1, []string{`tag1`, `tag2`}) 7 | m1 := struct { 8 | Action string `json:"action"` 9 | Id int `json:"item_id"` 10 | Tags string `json:"tags"` 11 | }{ 12 | Action: "tags_replace", 13 | Id: 1, 14 | Tags: `tag1,tag2`, 15 | } 16 | 17 | ValidateJSONs(t, a, m1) 18 | 19 | a.SetTS("time") 20 | m2 := struct { 21 | Action string `json:"action"` 22 | Id int `json:"item_id"` 23 | Time string `json:"timestamp"` 24 | Tags string `json:"tags"` 25 | }{ 26 | Action: "tags_replace", 27 | Id: 1, 28 | Tags: `tag1,tag2`, 29 | Time: "time", 30 | } 31 | 32 | ValidateJSONs(t, a, m2) 33 | } 34 | -------------------------------------------------------------------------------- /commands/modify/tags_add.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "encoding/json" 4 | 5 | type TagsAdd struct { 6 | *Protocol 7 | *ProtocolTags 8 | } 9 | 10 | func FactoryTagsAdd(id int, tags []string) *TagsAdd { 11 | p := &Protocol{Id: id} 12 | pt := &ProtocolTags{} 13 | pt.SetTags(tags) 14 | return NewTagsAdd(p, pt) 15 | } 16 | 17 | func NewTagsAdd(p *Protocol, pt *ProtocolTags) *TagsAdd { 18 | return &TagsAdd{Protocol: p, ProtocolTags: pt} 19 | } 20 | 21 | func (a *TagsAdd) MarshalJSON() ([]byte, error) { 22 | m := struct { 23 | Action string `json:"action"` 24 | *Protocol 25 | *ProtocolTags 26 | }{ 27 | Action: "tags_add", 28 | Protocol: a.Protocol, 29 | ProtocolTags: a.ProtocolTags, 30 | } 31 | 32 | return json.Marshal(m) 33 | } 34 | -------------------------------------------------------------------------------- /commands/modify/tags_clear.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "encoding/json" 4 | 5 | type TagsClear struct { 6 | *Protocol 7 | } 8 | 9 | func FactoryTagsClear(id int) *TagsClear { 10 | p := &Protocol{Id: id} 11 | return NewTagsClear(p) 12 | } 13 | 14 | func NewTagsClear(p *Protocol) *TagsClear { 15 | return &TagsClear{Protocol: p} 16 | } 17 | 18 | func (a *TagsClear) MarshalJSON() ([]byte, error) { 19 | m := struct { 20 | Action string `json:"action"` 21 | *Protocol 22 | }{ 23 | Action: "tags_clear", 24 | Protocol: a.Protocol, 25 | } 26 | 27 | return json.Marshal(m) 28 | } 29 | -------------------------------------------------------------------------------- /commands/modify/tags_remove.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "encoding/json" 4 | 5 | type TagsRemove struct { 6 | *Protocol 7 | *ProtocolTags 8 | } 9 | 10 | func FactoryTagsRemove(id int, tags []string) *TagsRemove { 11 | p := &Protocol{Id: id} 12 | pt := &ProtocolTags{} 13 | pt.SetTags(tags) 14 | return NewTagsRemove(p, pt) 15 | } 16 | 17 | func NewTagsRemove(p *Protocol, pt *ProtocolTags) *TagsRemove { 18 | return &TagsRemove{Protocol: p, ProtocolTags: pt} 19 | } 20 | 21 | func (a *TagsRemove) MarshalJSON() ([]byte, error) { 22 | m := struct { 23 | Action string `json:"action"` 24 | *Protocol 25 | *ProtocolTags 26 | }{ 27 | Action: "tags_remove", 28 | Protocol: a.Protocol, 29 | ProtocolTags: a.ProtocolTags, 30 | } 31 | 32 | return json.Marshal(m) 33 | } 34 | -------------------------------------------------------------------------------- /commands/modify/tags_rename.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "encoding/json" 4 | 5 | type TagsRename struct { 6 | *Protocol 7 | OldTag string 8 | NewTag string 9 | } 10 | 11 | func FactoryTagsRename(id int, oldTag, newTag string) *TagsRename { 12 | p := &Protocol{Id: id} 13 | return NewTagsRename(p, oldTag, newTag) 14 | } 15 | 16 | func NewTagsRename(p *Protocol, oldTag, newTag string) *TagsRename { 17 | return &TagsRename{Protocol: p, OldTag: oldTag, NewTag: newTag} 18 | } 19 | 20 | func (a *TagsRename) MarshalJSON() ([]byte, error) { 21 | m := struct { 22 | Action string `json:"action"` 23 | *Protocol 24 | OldTag string `json:"old_tag"` 25 | NewTag string `json:"new_tag"` 26 | }{ 27 | Action: "tags_rename", 28 | Protocol: a.Protocol, 29 | OldTag: a.OldTag, 30 | NewTag: a.NewTag, 31 | } 32 | 33 | return json.Marshal(m) 34 | } 35 | -------------------------------------------------------------------------------- /commands/modify/unfavorite.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "encoding/json" 4 | 5 | type Unfavorite struct { 6 | *Protocol 7 | } 8 | 9 | func FactoryUnfavorite(id int) *Unfavorite { 10 | p := &Protocol{Id: id} 11 | return NewUnfavorite(p) 12 | } 13 | 14 | func NewUnfavorite(p *Protocol) *Unfavorite { 15 | return &Unfavorite{Protocol: p} 16 | } 17 | 18 | func (a *Unfavorite) MarshalJSON() ([]byte, error) { 19 | m := struct { 20 | Action string `json:"action"` 21 | *Protocol 22 | }{ 23 | Action: "unfavorite", 24 | Protocol: a.Protocol, 25 | } 26 | 27 | return json.Marshal(m) 28 | } 29 | -------------------------------------------------------------------------------- /commands/modify/unfavorite_test.go: -------------------------------------------------------------------------------- 1 | package modify 2 | 3 | import "testing" 4 | 5 | func TestUnfavoriteMarshalJSON(t *testing.T) { 6 | a := FactoryUnfavorite(1) 7 | m1 := struct { 8 | Action string `json:"action"` 9 | Id int `json:"item_id"` 10 | }{ 11 | Action: "unfavorite", 12 | Id: 1, 13 | } 14 | 15 | ValidateJSONs(t, a, m1) 16 | 17 | a.SetTS("time") 18 | m2 := struct { 19 | Action string `json:"action"` 20 | Id int `json:"item_id"` 21 | Time string `json:"timestamp"` 22 | }{ 23 | Action: "unfavorite", 24 | Id: 1, 25 | Time: "time", 26 | } 27 | 28 | ValidateJSONs(t, a, m2) 29 | } 30 | -------------------------------------------------------------------------------- /commands/modify_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/Shaked/getpocket/auth" 8 | "github.com/Shaked/getpocket/commands/modify" 9 | "github.com/Shaked/getpocket/utils" 10 | ) 11 | 12 | type ActionStub struct{} 13 | 14 | func (a *ActionStub) MarshalJSON() ([]byte, error) { 15 | return nil, errors.New("error") 16 | } 17 | 18 | func TestModifyExec(t *testing.T) { 19 | user := auth.NewUser().SetAccessToken("access_token").SetUsername("username") 20 | r := &request{ret: "{}"} 21 | actions := []modify.Action{&ActionStub{}} 22 | a := NewModify("consumerKey", r, actions) 23 | d, err := a.Exec(user) 24 | if nil == err { 25 | t.Errorf("error %s", d) 26 | } 27 | action1 := modify.FactoryFavorite(1) 28 | action2 := modify.FactoryFavorite(2) 29 | actions = []modify.Action{action1, action2} 30 | a = NewModify("consumerKey", r, actions) 31 | _, err = a.Exec(user) 32 | if nil != err { 33 | t.Errorf("error %s", err) 34 | } 35 | 36 | r = &request{err: utils.NewRequestError(1, errors.New("just an error"))} 37 | a = NewModify("consumerKey", r, actions) 38 | _, err = a.Exec(user) 39 | if nil == err { 40 | t.Fail() 41 | } 42 | 43 | r = &request{ret: "\n"} 44 | a = NewModify("consumerKey", r, actions) 45 | _, err = a.Exec(user) 46 | if nil == err { 47 | t.Fail() 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /commands/retrieve.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/url" 8 | "time" 9 | 10 | "github.com/Shaked/getpocket/auth" 11 | "github.com/Shaked/getpocket/utils" 12 | ) 13 | 14 | const ( 15 | STATE_UNREAD = "unread" 16 | STATE_ARCHIVE = "archive" 17 | STATE_ALL = "all" 18 | 19 | TAG_UNTAGGED = "_untagged_" 20 | 21 | CONTENT_TYPE_ARTICLE = "article" 22 | CONTENT_TYPE_VIDEO = "video" 23 | CONTENT_TYPE_IMAGE = "image" 24 | 25 | SORT_NEWEST = "newest" 26 | SORT_OLDEST = "oldest" 27 | SORT_TITLE = "title" 28 | SORT_SITE = "site" 29 | 30 | DETAIL_TYPE_SIMPLE = "simple" 31 | DETAIL_TYPE_COMPLETE = "complete" 32 | ) 33 | 34 | var ( 35 | states = map[string]bool{ 36 | STATE_ALL: true, 37 | STATE_ARCHIVE: true, 38 | STATE_UNREAD: true, 39 | } 40 | 41 | contentTypes = map[string]bool{ 42 | CONTENT_TYPE_ARTICLE: true, 43 | CONTENT_TYPE_IMAGE: true, 44 | CONTENT_TYPE_VIDEO: true, 45 | } 46 | 47 | sorts = map[string]bool{ 48 | SORT_NEWEST: true, 49 | SORT_OLDEST: true, 50 | SORT_SITE: true, 51 | SORT_TITLE: true, 52 | } 53 | 54 | detailTypes = map[string]bool{ 55 | DETAIL_TYPE_COMPLETE: true, 56 | DETAIL_TYPE_SIMPLE: true, 57 | } 58 | ) 59 | 60 | //@see http://getpocket.com/developer/docs/v3/retrieve 61 | type Retrieve struct { 62 | command 63 | 64 | state string 65 | favorite bool 66 | tag string 67 | contentType string 68 | sort string 69 | detailType string 70 | search string 71 | domain string 72 | since time.Time 73 | count int 74 | offset int 75 | } 76 | 77 | type RetrieveResponse struct { 78 | List map[string]RetrieveItem `json:"list"` 79 | Status int `json:"status"` 80 | Complete int `json:"complete"` 81 | } 82 | 83 | type RetrieveItem struct { 84 | Id string `json:"item_id"` 85 | ResolvedId string `json:"resolved_id"` 86 | GivenURL string `json:"given_url"` 87 | ResolvedURL string `json:"resolved_url"` 88 | GivenTitle string `json:"given_title"` 89 | ResolvedTitle string `json:"resolved_title"` 90 | Favorite string `json:"favorite"` 91 | Status string `json:"status"` 92 | Excerpt string `json:"excerpt"` 93 | IsArticle string `json:"is_article"` 94 | HasImage string `json:"has_image"` 95 | HasVideo string `json:"has_video"` 96 | WordCount string `json:"word_count"` 97 | TimeAdded string `json:"time_added"` 98 | TimeUpdated string `json:"time_updated"` 99 | TimeRead string `json:"time_read"` 100 | TimeFavorited string `json:"time_favorited"` 101 | SortId int `json:"sort_id"` 102 | IsIndex string `json:"is_index"` 103 | Tags map[string]ItemTag `json:"tags"` 104 | Authors map[string]ItemAuthor `json:"authors"` 105 | Images map[string]ItemImage `json:"images"` 106 | Videos map[string]ItemVideo `json:"videos"` 107 | } 108 | 109 | func NewRetrieve(consumerKey string, request utils.HttpRequest) *Retrieve { 110 | r := &Retrieve{} 111 | r.SetConsumerKey(consumerKey) 112 | r.SetRequest(request) 113 | return r 114 | } 115 | 116 | func (c *Retrieve) SetState(state string) error { 117 | if !states[state] { 118 | return errors.New(fmt.Sprintf("State %s does not exist", state)) 119 | } 120 | c.state = state 121 | return nil 122 | } 123 | 124 | func (c *Retrieve) SetFavorite(favorite bool) *Retrieve { 125 | c.favorite = favorite 126 | return c 127 | } 128 | 129 | func (c *Retrieve) SetTag(tag string) *Retrieve { 130 | c.tag = tag 131 | return c 132 | } 133 | 134 | func (c *Retrieve) SetUntagged() *Retrieve { 135 | c.SetTag(TAG_UNTAGGED) 136 | return c 137 | } 138 | 139 | func (c *Retrieve) SetContentType(contentType string) error { 140 | if !contentTypes[contentType] { 141 | return errors.New(fmt.Sprintf("ContentType %s does not exist", contentType)) 142 | } 143 | c.contentType = contentType 144 | return nil 145 | } 146 | 147 | func (c *Retrieve) SetSort(sort string) error { 148 | if !sorts[sort] { 149 | return errors.New(fmt.Sprintf("sort %s does not exist", sort)) 150 | } 151 | c.sort = sort 152 | return nil 153 | } 154 | 155 | func (c *Retrieve) SetDetailType(detailType string) error { 156 | if !detailTypes[detailType] { 157 | return errors.New(fmt.Sprintf("detailType %s does not exist", detailType)) 158 | } 159 | c.detailType = detailType 160 | return nil 161 | } 162 | 163 | func (c *Retrieve) Exec(user auth.Authenticated) (*RetrieveResponse, error) { 164 | u := url.Values{} 165 | u.Add("consumer_key", c.consumerKey) 166 | u.Add("access_token", user.AccessToken()) 167 | 168 | if "" != c.state { 169 | u.Add("state", c.state) 170 | } 171 | 172 | if c.favorite { 173 | u.Add("favorite", "1") 174 | } 175 | 176 | if "" != c.tag { 177 | u.Add("tag", c.tag) 178 | } 179 | 180 | if "" != c.contentType { 181 | u.Add("contentType", c.contentType) 182 | } 183 | 184 | if "" != c.sort { 185 | u.Add("sort", c.sort) 186 | } 187 | 188 | if "" != c.detailType { 189 | u.Add("detailType", c.detailType) 190 | } 191 | 192 | body, err := c.request.Post(URLs["Retrieve"], u) 193 | if nil != err { 194 | return nil, err 195 | } 196 | 197 | resp := &RetrieveResponse{} 198 | e := json.Unmarshal(fixJSONArrayToObject(body), resp) 199 | if nil != e { 200 | return nil, e 201 | } 202 | return resp, nil 203 | } 204 | -------------------------------------------------------------------------------- /commands/retrieve_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/Shaked/getpocket/auth" 8 | "github.com/Shaked/getpocket/utils" 9 | ) 10 | 11 | func TestNewRetrieve(t *testing.T) { 12 | r := NewRetrieve("consumerKey", nil) 13 | 14 | req := &request{ret: "{}"} 15 | r = NewRetrieve("consumerKey", req) 16 | 17 | r.SetContentType(CONTENT_TYPE_VIDEO) 18 | if CONTENT_TYPE_VIDEO != r.contentType { 19 | t.Errorf("Content type setter is broken, set to: %s", r.contentType) 20 | } 21 | r.SetDetailType(DETAIL_TYPE_SIMPLE) 22 | if DETAIL_TYPE_SIMPLE != r.detailType { 23 | t.Errorf("Detail type setter is broken, set to: %s", r.detailType) 24 | } 25 | r.SetSort(SORT_SITE) 26 | if SORT_SITE != r.sort { 27 | t.Errorf("Sort setter is broken, set to: %s", r.sort) 28 | } 29 | r.SetState(STATE_UNREAD) 30 | if STATE_UNREAD != r.state { 31 | t.Errorf("State setter is broken, set to: %s", r.state) 32 | } 33 | r.SetTag("tag").SetFavorite(true) 34 | if "tag" != r.tag { 35 | t.Errorf("Tag setter is broken, set to: %s", r.tag) 36 | } 37 | if true != r.favorite { 38 | t.Errorf("Favorite setter is broken, set to: %s", r.favorite) 39 | } 40 | 41 | user := auth.NewUser().SetAccessToken("access_token").SetUsername("username") 42 | 43 | _, err := r.Exec(user) 44 | if nil != err { 45 | t.Errorf("error %s", err) 46 | } 47 | 48 | req = &request{err: utils.NewRequestError(1, errors.New("just an error"))} 49 | r = NewRetrieve("consumerKey", req) 50 | _, err = r.Exec(user) 51 | if nil == err { 52 | t.Fail() 53 | } 54 | 55 | req = &request{ret: "\n"} 56 | r = NewRetrieve("consumerKey", req) 57 | _, err = r.Exec(user) 58 | if nil == err { 59 | t.Fail() 60 | } 61 | } 62 | 63 | func TestSettersError(t *testing.T) { 64 | req := &request{ret: "{}"} 65 | r := NewRetrieve("consumer-key", req) 66 | var e error 67 | e = r.SetContentType("contentType") 68 | if nil == e { 69 | t.Error("Invalid content type should return an error") 70 | } 71 | e = r.SetDetailType("detailType") 72 | if nil == e { 73 | t.Error("Invalid detail type should return an error") 74 | } 75 | e = r.SetSort("sort") 76 | if nil == e { 77 | t.Error("Invalid sort should return an error") 78 | } 79 | e = r.SetState("state") 80 | if nil == e { 81 | t.Error("Invalid state should return an error") 82 | } 83 | 84 | r.SetUntagged() 85 | if TAG_UNTAGGED != r.tag { 86 | t.Errorf("Tag should be %s when set to untagged", TAG_UNTAGGED) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 github.com/Shaked Author. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package Shaked/getpocket is an implementation for Getpocket.com's API. 7 | The Package is documented at: 8 | 9 | http://godoc.org/github.com/Shaked/getpocket 10 | 11 | Let's start with authenticating to Getpocket using Shaked/getpocket package: 12 | 13 | type Page struct { 14 | auth *auth.Auth 15 | } 16 | 17 | var ( 18 | redirectURI = "https://localhost:10443/authcheck" 19 | consumerKey = "yourconsumerkeyhere" 20 | ssl_certificate = "ssl/server.crt" 21 | ssl_key = "ssl/server.key" 22 | ) 23 | 24 | func main() { 25 | var e *utils.RequestError 26 | a, e := auth.Factory(consumerKey, redirectURI) 27 | if nil != e { 28 | log.Fatal(e) 29 | } 30 | log.Printf("Listen on 10443") 31 | p := &Page{auth: a} 32 | http.HandleFunc("/auth", p.Auth) 33 | http.HandleFunc("/authcheck", p.AuthCheck) 34 | 35 | err := http.ListenAndServeTLS(":10443", ssl_certificate, ssl_key, nil) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | } 40 | 41 | func (p *Page) Auth(w http.ResponseWriter, r *http.Request) { 42 | log.Println(r.Referer(), "Example Handler, Should connect and request app permissions") 43 | requestToken, err := p.auth.Connect() 44 | if nil != err { 45 | fmt.Fprintf(w, "Token error %s (%d)", err.Error(), err.ErrorCode()) 46 | } 47 | 48 | p.auth.RequestPermissions(requestToken, w, r) 49 | } 50 | 51 | func (p *Page) AuthCheck(w http.ResponseWriter, r *http.Request) { 52 | log.Println(r.Referer(), "GetPocket connection check, should get the username and access token") 53 | requestToken := r.URL.Query().Get("requestToken") 54 | if "" == requestToken { 55 | fmt.Fprintf(w, "Request token is invalid") 56 | return 57 | } 58 | user, err := p.auth.User(requestToken) 59 | if nil != err { 60 | fmt.Fprintf(w, "%s (%d)", err.Error(), err.ErrorCode()) 61 | return 62 | } 63 | fmt.Fprintf(w, "%#v", user) 64 | } 65 | 66 | Later on we can apply Getpocket's commands. 67 | 68 | Add: 69 | 70 | c := commands.New(user, consumerKey) 71 | u := "http://confreaks.com/videos/3432-gophercon2014-go-from-c-to-go" 72 | add := commands.NewAdd(u) 73 | add.SetTitle("Some cool title").SetTags("Shaked,Blog") 74 | resp, e := c.Exec(add) 75 | if nil != e { 76 | fmt.Fprintf(w, "ERROR%s\n", e) 77 | return 78 | } 79 | 80 | Retrieve: 81 | 82 | retrieve := commands.NewRetrieve() 83 | resp, e := c.Exec(retrieve) 84 | if nil != e { 85 | fmt.Fprintf(w, "ERROR%s\n", e) 86 | return 87 | } 88 | 89 | Modify: 90 | 91 | id := 12345678 92 | favorite := modify.FactoryFavorite(id) 93 | unfavorite := modify.FactoryUnfavorite(id) 94 | add := modify.FactoryAdd(id) 95 | add.SetTags([]string{"go", "programming", "blog"}) 96 | actions := []modify.Action{ 97 | add, 98 | favorite, 99 | unfavorite, 100 | } 101 | modify := commands.NewModify(actions) 102 | c := commands.New(user, consumerKey) 103 | resp, e := c.Exec(modify) 104 | if nil != e { 105 | fmt.Fprintf(w, "ERROR%s\n", e) 106 | } 107 | 108 | The modify command contains the following actions: 109 | 110 | add 111 | archive 112 | delete 113 | favorite 114 | unfavorite 115 | readd 116 | tag_add 117 | tag_clear 118 | tag_remove 119 | tag_rename 120 | tag_replace 121 | 122 | Check the repository for more details and examples: 123 | 124 | https://www.github.com/Shaked/getpocket 125 | 126 | */ 127 | package getpocket 128 | -------------------------------------------------------------------------------- /getpocket.go: -------------------------------------------------------------------------------- 1 | package getpocket 2 | -------------------------------------------------------------------------------- /utils/error.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | type RequesterError interface { 4 | ErrorCode() int 5 | } 6 | 7 | type RequestError struct { 8 | error 9 | RequesterError 10 | xErrorCode int 11 | xErrorText error 12 | } 13 | 14 | func NewRequestError(xErrorCode int, xErrorText error) *RequestError { 15 | return &RequestError{ 16 | xErrorCode: xErrorCode, 17 | xErrorText: xErrorText, 18 | } 19 | } 20 | 21 | func (e *RequestError) Error() string { 22 | return e.xErrorText.Error() 23 | } 24 | 25 | func (e *RequestError) ErrorCode() int { 26 | return e.xErrorCode 27 | } 28 | -------------------------------------------------------------------------------- /utils/error_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | func TestNewRequestError(t *testing.T) { 9 | e := NewRequestError(100, errors.New("some error")) 10 | if "some error" != e.Error() { 11 | t.Fatalf("Error method returned: %s", e.Error()) 12 | } 13 | if 100 != e.ErrorCode() { 14 | t.Fatalf("ErrorCode method returned %d", e.ErrorCode()) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net/http" 7 | "net/url" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type HttpRequest interface { 13 | Post(url string, values url.Values) ([]byte, *RequestError) 14 | } 15 | 16 | type Request struct{} 17 | 18 | func NewRequest() *Request { 19 | return &Request{} 20 | } 21 | 22 | func (ur *Request) Post(url string, values url.Values) ([]byte, *RequestError) { 23 | client := &http.Client{} 24 | r, err := http.NewRequest("POST", url, strings.NewReader(values.Encode())) 25 | if nil != err { 26 | return nil, NewRequestError(http.StatusInternalServerError, err) 27 | } 28 | 29 | headers := ur.getHeaders() 30 | for header, value := range headers { 31 | r.Header.Set(header, value) 32 | } 33 | 34 | resp, err := client.Do(r) 35 | if nil != err { 36 | return nil, NewRequestError(http.StatusInternalServerError, err) 37 | } 38 | defer resp.Body.Close() 39 | body, err := ioutil.ReadAll(resp.Body) 40 | if nil != err { 41 | return nil, NewRequestError(http.StatusInternalServerError, err) 42 | } 43 | if resp.StatusCode != http.StatusOK { 44 | xErrorCode, _ := strconv.ParseInt(resp.Header.Get("X-Error-Code"), 10, 0) 45 | xErrorText := resp.Header.Get("X-Error") 46 | return nil, NewRequestError(int(xErrorCode), errors.New(xErrorText)) 47 | } 48 | return body, nil 49 | } 50 | 51 | func (ur *Request) getHeaders() map[string]string { 52 | return map[string]string{ 53 | "Content-Type": "application/x-www-form-urlencoded", 54 | "X-Accept": "application/json", 55 | } 56 | } 57 | --------------------------------------------------------------------------------