├── .appveyor.yml ├── .gitignore ├── .travis.yml ├── COPYING ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── README ├── README.md ├── account.go ├── backoff.go ├── blocks.go ├── configuration.go ├── directmessage.go ├── directmessages.go ├── errors.go ├── example_test.go ├── favorites.go ├── friends_followers.go ├── geosearch.go ├── json ├── account │ └── verify_credentials.json ├── direct_messages │ └── new.json ├── favorites │ └── list.json ├── followers │ ├── ids.json │ └── list.json ├── friends │ └── ids.json ├── lists │ ├── members │ │ ├── destroy.json │ │ └── destroy_all.json │ └── ownerships.json ├── search │ └── tweets.json ├── statuses │ ├── oembed.json │ ├── show.json │ ├── show.json_id_404409873170841600_tweet_mode_extended │ └── show.json_id_738567564641599489_tweet_mode_extended └── users │ └── lookup.json ├── list.go ├── lists.go ├── log.go ├── media.go ├── mutes.go ├── oembed.go ├── oembed_test.go ├── place.go ├── rate_limit_status.go ├── relationship.go ├── search.go ├── streaming.go ├── timeline.go ├── trends.go ├── tweet.go ├── tweets.go ├── twitter.go ├── twitter_entities.go ├── twitter_test.go ├── twitter_user.go ├── users.go ├── vendor └── github.com │ ├── ChimeraCoder │ └── tokenbucket │ │ ├── .gitignore │ │ ├── COPYING │ │ ├── LICENSE │ │ ├── README │ │ ├── README.md │ │ ├── tokenbucket.go │ │ └── tokenbucket_test.go │ ├── azr │ └── backoff │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── backoff.go │ │ ├── backoff_test.go │ │ ├── example_test.go │ │ ├── exponential.go │ │ ├── exponential_test.go │ │ ├── linear.go │ │ └── linear_test.go │ ├── dustin │ ├── go-jsonpointer │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.markdown │ │ ├── bytes.go │ │ ├── bytes_test.go │ │ ├── doc.go │ │ ├── map.go │ │ ├── map_test.go │ │ ├── ptrtool │ │ │ └── ptrtool.go │ │ ├── reflect.go │ │ ├── reflect_test.go │ │ └── testdata │ │ │ ├── 357.json │ │ │ ├── code.json.gz │ │ │ ├── pools.json │ │ │ └── serieslysample.json │ └── gojson │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── bench_test.go │ │ ├── decode.go │ │ ├── decode_test.go │ │ ├── encode.go │ │ ├── encode_test.go │ │ ├── example_test.go │ │ ├── fold.go │ │ ├── fold_test.go │ │ ├── indent.go │ │ ├── scanner.go │ │ ├── scanner_test.go │ │ ├── stream.go │ │ ├── stream_test.go │ │ ├── tagkey_test.go │ │ ├── tags.go │ │ ├── tags_test.go │ │ └── testdata │ │ └── code.json.gz │ └── garyburd │ └── go-oauth │ ├── .gitignore │ ├── .travis.yml │ ├── README.markdown │ ├── examples │ ├── appengine │ │ ├── README.markdown │ │ ├── app.go │ │ ├── app.yaml │ │ └── config.json.example │ ├── discogs │ │ ├── README.markdown │ │ ├── config.json.example │ │ └── main.go │ ├── dropbox │ │ ├── README.markdown │ │ ├── config.json.example │ │ └── main.go │ ├── quickbooks │ │ ├── README.markdown │ │ ├── config.json.example │ │ └── main.go │ ├── session │ │ └── session.go │ ├── smugmug │ │ ├── README.markdown │ │ ├── config.json.example │ │ └── main.go │ ├── twitter │ │ ├── README.markdown │ │ ├── config.json.example │ │ └── main.go │ ├── twitteroob │ │ ├── README.markdown │ │ ├── config.json.example │ │ └── main.go │ └── yelp │ │ ├── README.markdown │ │ ├── config.json.example │ │ └── main.go │ └── oauth │ ├── examples_test.go │ ├── oauth.go │ └── oauth_test.go └── webhook.go /.appveyor.yml: -------------------------------------------------------------------------------- 1 | clone_folder: c:\gopath\src\github.com\ChimeraCoder\anaconda 2 | 3 | environment: 4 | GOPATH: c:\gopath 5 | 6 | build_script: 7 | - go get . 8 | - go test -race -v -timeout 120s 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.swn 4 | conf.sh 5 | *.patch 6 | anaconda.test 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | os: 4 | - linux 5 | - osx 6 | 7 | install: 8 | # Use gofmt from Go 1.9 for the pre-build check on all builds 9 | - if [ "$TRAVIS_OS_NAME" = "osx" ]; then wget -O go.tar.gz https://storage.googleapis.com/golang/go1.9.darwin-amd64.tar.gz; else wget -O go.tar.gz https://storage.googleapis.com/golang/go1.9.linux-amd64.tar.gz; fi 10 | - tar -C /tmp -xvf go.tar.gz go/bin/gofmt 11 | - rm go.tar.gz 12 | 13 | before_script: 14 | - /tmp/go/bin/gofmt -w . 15 | 16 | # If `go generate` or `gofmt` yielded any changes, 17 | # this will fail with an error message like "too many arguments" 18 | # or "M: binary operator expected" and show the diff. 19 | - git diff 20 | - git add . 21 | - git diff-index --cached --exit-code HEAD 22 | 23 | go: 24 | - 1.6 25 | - 1.7 26 | - 1.8 27 | - 1.9 28 | - tip 29 | 30 | matrix: 31 | exclude: 32 | - os: osx 33 | go: 1.6 34 | 35 | script: 36 | - echo $TRAVIS_GO_VERSION 37 | - if [ "$TRAVIS_GO_VERSION" == "1.6" ] || [ "$TRAVIS_GO_VERSION" == "1.7" ] || [ "$TRAVIS_GO_VERSION" == "1.8" ]; then go list ./... | grep -v vendor | xargs go test -race -v -timeout 60s; else go test -race -v -timeout 60s ./...; fi 38 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | LICENSE -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | branch = "master" 6 | name = "github.com/ChimeraCoder/tokenbucket" 7 | packages = ["."] 8 | revision = "c5a927568de7aad8a58127d80bcd36ca4e71e454" 9 | 10 | [[projects]] 11 | branch = "master" 12 | name = "github.com/azr/backoff" 13 | packages = ["."] 14 | revision = "53511d3c733003985b0b76f733df1f4d0095ee6a" 15 | 16 | [[projects]] 17 | branch = "master" 18 | name = "github.com/dustin/go-jsonpointer" 19 | packages = ["."] 20 | revision = "ba0abeacc3dcca5b9b20f31509c46794edbc9965" 21 | 22 | [[projects]] 23 | branch = "master" 24 | name = "github.com/dustin/gojson" 25 | packages = ["."] 26 | revision = "2e71ec9dd5adce3b168cd0dbde03b5cc04951c30" 27 | 28 | [[projects]] 29 | branch = "master" 30 | name = "github.com/garyburd/go-oauth" 31 | packages = ["oauth"] 32 | revision = "166ce8d672783fbb5a72247c3cf459267717e1ec" 33 | 34 | [solve-meta] 35 | analyzer-name = "dep" 36 | analyzer-version = 1 37 | inputs-digest = "e645e975b86556d43a1fb9a6aacbaa500a8549d3262d8421baca41f04ae42f4f" 38 | solver-name = "gps-cdcl" 39 | solver-version = 1 40 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | 2 | # Gopkg.toml example 3 | # 4 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 5 | # for detailed Gopkg.toml documentation. 6 | # 7 | # required = ["github.com/user/thing/cmd/thing"] 8 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 9 | # 10 | # [[constraint]] 11 | # name = "github.com/user/project" 12 | # version = "1.0.0" 13 | # 14 | # [[constraint]] 15 | # name = "github.com/user/project2" 16 | # branch = "dev" 17 | # source = "github.com/myfork/project2" 18 | # 19 | # [[override]] 20 | # name = "github.com/x/y" 21 | # version = "2.4.0" 22 | 23 | 24 | [[constraint]] 25 | branch = "master" 26 | name = "github.com/ChimeraCoder/tokenbucket" 27 | 28 | [[constraint]] 29 | branch = "master" 30 | name = "github.com/azr/backoff" 31 | 32 | [[constraint]] 33 | branch = "master" 34 | name = "github.com/dustin/go-jsonpointer" 35 | 36 | [[constraint]] 37 | branch = "master" 38 | name = "github.com/garyburd/go-oauth" 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Aditya Mukerjee, Quotidian Ventures 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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 8 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Anaconda 2 | ======== 3 | 4 | [![Build Status](https://travis-ci.org/ChimeraCoder/anaconda.svg?branch=master)](https://travis-ci.org/ChimeraCoder/anaconda) [![Build Status](https://ci.appveyor.com/api/projects/status/63pi6csod8bps80i/branch/master?svg=true)](https://ci.appveyor.com/project/ChimeraCoder/anaconda/branch/master) [![GoDoc](https://godoc.org/github.com/ChimeraCoder/anaconda?status.svg)](https://godoc.org/github.com/ChimeraCoder/anaconda) 5 | 6 | Anaconda is a simple, transparent Go package for accessing version 1.1 of the Twitter API. 7 | 8 | Successful API queries return native Go structs that can be used immediately, with no need for type assertions. 9 | 10 | 11 | 12 | Examples 13 | -------- 14 | 15 | ### Authentication 16 | 17 | If you already have the access token (and secret) for your user (Twitter provides this for your own account on the developer portal), creating the client is simple: 18 | 19 | ```go 20 | api := anaconda.NewTwitterApiWithCredentials("your-access-token", "your-access-token-secret", "your-consumer-key", "your-consumer-secret") 21 | ``` 22 | 23 | ### Queries 24 | 25 | Queries are conducted using a pointer to an authenticated `TwitterApi` struct. In v1.1 of Twitter's API, all requests should be authenticated. 26 | 27 | ```go 28 | searchResult, _ := api.GetSearch("golang", nil) 29 | for _ , tweet := range searchResult.Statuses { 30 | fmt.Println(tweet.Text) 31 | } 32 | ``` 33 | Certain endpoints allow separate optional parameter; if desired, these can be passed as the final parameter. 34 | 35 | ```go 36 | //Perhaps we want 30 values instead of the default 15 37 | v := url.Values{} 38 | v.Set("count", "30") 39 | result, err := api.GetSearch("golang", v) 40 | ``` 41 | 42 | (Remember that `url.Values` is equivalent to a `map[string][]string`, if you find that more convenient notation when specifying values). Otherwise, `nil` suffices. 43 | 44 | ### Streaming 45 | 46 | Anaconda supports the Streaming APIs. You can use `PublicStream*` or `UserStream` API methods. 47 | A go loop is started an gives you an stream that sends `interface{}` objects through it's `chan` `C` 48 | Objects which you can cast into a tweet, event and more. 49 | 50 | 51 | ````go 52 | v := url.Values{} 53 | s := api.UserStream(v) 54 | 55 | for t := range s.C { 56 | switch v := t.(type) { 57 | case anaconda.Tweet: 58 | fmt.Printf("%-15s: %s\n", v.User.ScreenName, v.Text) 59 | case anaconda.EventTweet: 60 | switch v.Event.Event { 61 | case "favorite": 62 | sn := v.Source.ScreenName 63 | tw := v.TargetObject.Text 64 | fmt.Printf("Favorited by %-15s: %s\n", sn, tw) 65 | case "unfavorite": 66 | sn := v.Source.ScreenName 67 | tw := v.TargetObject.Text 68 | fmt.Printf("UnFavorited by %-15s: %s\n", sn, tw) 69 | } 70 | } 71 | } 72 | ```` 73 | 74 | 75 | 76 | Endpoints 77 | --------- 78 | 79 | Anaconda implements most of the endpoints defined in the [Twitter API documentation](https://developer.twitter.com/en/docs). For clarity, in most cases, the function name is simply the name of the HTTP method and the endpoint (e.g., the endpoint `GET /friendships/incoming` is provided by the function `GetFriendshipsIncoming`). 80 | 81 | In a few cases, a shortened form has been chosen to make life easier (for example, retweeting is simply the function `Retweet`) 82 | 83 | 84 | 85 | Error Handling, Rate Limiting, and Throttling 86 | --------------------------------------------- 87 | 88 | ### Error Handling 89 | 90 | Twitter errors are returned as an `ApiError`, which satisfies the `error` interface and can be treated as a vanilla `error`. However, it also contains the additional information returned by the Twitter API that may be useful in deciding how to proceed after encountering an error. 91 | 92 | 93 | If you make queries too quickly, you may bump against Twitter's [rate limits](https://developer.twitter.com/en/docs/basics/rate-limits). If this happens, `anaconda` automatically retries the query when the rate limit resets, using the `X-Rate-Limit-Reset` header that Twitter provides to determine how long to wait. 94 | 95 | In other words, users of the `anaconda` library should not need to handle rate limiting errors themselves; this is handled seamlessly behind-the-scenes. If an error is returned by a function, another form of error must have occurred (which can be checked by using the fields provided by the `ApiError` struct). 96 | 97 | 98 | (If desired, this feature can be turned off by calling `ReturnRateLimitError(true)`.) 99 | 100 | 101 | ### Throttling 102 | 103 | Anaconda now supports automatic client-side throttling of queries to avoid hitting the Twitter rate-limit. 104 | 105 | This is currently *off* by default; however, it may be turned on by default in future versions of the library, as the implementation is improved. 106 | 107 | 108 | To set a delay between queries, use the `SetDelay` method: 109 | 110 | ```go 111 | api.SetDelay(10 * time.Second) 112 | ``` 113 | 114 | Delays are set specific to each `TwitterApi` struct, so queries that use different users' access credentials are completely independent. 115 | 116 | 117 | To turn off automatic throttling, set the delay to `0`: 118 | 119 | ```go 120 | api.SetDelay(0 * time.Second) 121 | ``` 122 | 123 | ### Query Queue Persistence 124 | 125 | If your code creates a NewTwitterApi in a regularly called function, you'll need to call `.Close()` on the API struct to clear the queryQueue and allow the goroutine to exit. Otherwise you could see goroutine and therefor heap memory leaks in long-running applications. 126 | 127 | ### Google App Engine 128 | 129 | Since Google App Engine doesn't make the standard `http.Transport` available, it's necessary to tell Anaconda to use a different client context. 130 | 131 | ```go 132 | api = anaconda.NewTwitterApi("", "") 133 | c := appengine.NewContext(r) 134 | api.HttpClient.Transport = &urlfetch.Transport{Context: c} 135 | ``` 136 | 137 | 138 | License 139 | ------- 140 | Anaconda is free software licensed under the MIT/X11 license. Details provided in the LICENSE file. 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | README -------------------------------------------------------------------------------- /account.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | // Verify the credentials by making a very small request 8 | func (a TwitterApi) VerifyCredentials() (ok bool, err error) { 9 | v := cleanValues(nil) 10 | v.Set("include_entities", "false") 11 | v.Set("skip_status", "true") 12 | 13 | _, err = a.GetSelf(v) 14 | return err == nil, err 15 | } 16 | 17 | // Get the user object for the authenticated user. Requests /account/verify_credentials 18 | func (a TwitterApi) GetSelf(v url.Values) (u User, err error) { 19 | response_ch := make(chan response) 20 | a.queryQueue <- query{a.baseUrl + "/account/verify_credentials.json", v, &u, _GET, response_ch} 21 | return u, (<-response_ch).err 22 | } 23 | -------------------------------------------------------------------------------- /backoff.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/azr/backoff" 7 | ) 8 | 9 | /* 10 | Reconnecting(from https://developer.twitter.com/en/docs/tutorials/consuming-streaming-data) : 11 | 12 | Once an established connection drops, attempt to reconnect immediately. 13 | If the reconnect fails, slow down your reconnect attempts according to the type of error experienced: 14 | */ 15 | 16 | //Back off linearly for TCP/IP level network errors. 17 | // These problems are generally temporary and tend to clear quickly. 18 | // Increase the delay in reconnects by 250ms each attempt, up to 16 seconds. 19 | func NewTCPIPErrBackoff() backoff.Interface { 20 | return backoff.NewLinear(0, time.Second*16, time.Millisecond*250, 1) 21 | } 22 | 23 | //Back off exponentially for HTTP errors for which reconnecting would be appropriate. 24 | // Start with a 5 second wait, doubling each attempt, up to 320 seconds. 25 | func NewHTTPErrBackoff() backoff.Interface { 26 | eb := backoff.NewExponential() 27 | eb.InitialInterval = time.Second * 5 28 | eb.MaxInterval = time.Second * 320 29 | eb.Multiplier = 2 30 | eb.Reset() 31 | return eb 32 | } 33 | 34 | // Back off exponentially for HTTP 420 errors. 35 | // Start with a 1 minute wait and double each attempt. 36 | // Note that every HTTP 420 received increases the time you must 37 | // wait until rate limiting will no longer will be in effect for your account. 38 | func NewHTTP420ErrBackoff() backoff.Interface { 39 | eb := backoff.NewExponential() 40 | eb.InitialInterval = time.Minute * 1 41 | eb.Multiplier = 2 42 | eb.MaxInterval = time.Minute * 20 43 | eb.Reset() 44 | return eb 45 | } 46 | -------------------------------------------------------------------------------- /blocks.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | ) 7 | 8 | func (a TwitterApi) GetBlocksList(v url.Values) (c UserCursor, err error) { 9 | response_ch := make(chan response) 10 | a.queryQueue <- query{a.baseUrl + "/blocks/list.json", v, &c, _GET, response_ch} 11 | return c, (<-response_ch).err 12 | } 13 | 14 | func (a TwitterApi) GetBlocksIds(v url.Values) (c Cursor, err error) { 15 | response_ch := make(chan response) 16 | a.queryQueue <- query{a.baseUrl + "/blocks/ids.json", v, &c, _GET, response_ch} 17 | return c, (<-response_ch).err 18 | } 19 | 20 | func (a TwitterApi) BlockUser(screenName string, v url.Values) (user User, err error) { 21 | v = cleanValues(v) 22 | v.Set("screen_name", screenName) 23 | return a.Block(v) 24 | } 25 | 26 | func (a TwitterApi) BlockUserId(id int64, v url.Values) (user User, err error) { 27 | v = cleanValues(v) 28 | v.Set("user_id", strconv.FormatInt(id, 10)) 29 | return a.Block(v) 30 | } 31 | 32 | func (a TwitterApi) Block(v url.Values) (user User, err error) { 33 | response_ch := make(chan response) 34 | a.queryQueue <- query{a.baseUrl + "/blocks/create.json", v, &user, _POST, response_ch} 35 | return user, (<-response_ch).err 36 | } 37 | 38 | func (a TwitterApi) UnblockUser(screenName string, v url.Values) (user User, err error) { 39 | v = cleanValues(v) 40 | v.Set("screen_name", screenName) 41 | return a.Unblock(v) 42 | } 43 | 44 | func (a TwitterApi) UnblockUserId(id int64, v url.Values) (user User, err error) { 45 | v = cleanValues(v) 46 | v.Set("user_id", strconv.FormatInt(id, 10)) 47 | return a.Unblock(v) 48 | } 49 | 50 | func (a TwitterApi) Unblock(v url.Values) (user User, err error) { 51 | response_ch := make(chan response) 52 | a.queryQueue <- query{a.baseUrl + "/blocks/destroy.json", v, &user, _POST, response_ch} 53 | return user, (<-response_ch).err 54 | } 55 | -------------------------------------------------------------------------------- /configuration.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | type Configuration struct { 8 | CharactersReservedPerMedia int `json:"characters_reserved_per_media"` 9 | MaxMediaPerUpload int `json:"max_media_per_upload"` 10 | NonUsernamePaths []string `json:"non_username_paths"` 11 | PhotoSizeLimit int `json:"photo_size_limit"` 12 | PhotoSizes struct { 13 | Thumb photoSize `json:"thumb"` 14 | Small photoSize `json:"small"` 15 | Medium photoSize `json:"medium"` 16 | Large photoSize `json:"large"` 17 | } `json:"photo_sizes"` 18 | ShortUrlLength int `json:"short_url_length"` 19 | ShortUrlLengthHttps int `json:"short_url_length_https"` 20 | } 21 | 22 | type photoSize struct { 23 | H int `json:"h"` 24 | W int `json:"w"` 25 | Resize string `json:"resize"` 26 | } 27 | 28 | func (a TwitterApi) GetConfiguration(v url.Values) (conf Configuration, err error) { 29 | response_ch := make(chan response) 30 | a.queryQueue <- query{a.baseUrl + "/help/configuration.json", v, &conf, _GET, response_ch} 31 | return conf, (<-response_ch).err 32 | } 33 | -------------------------------------------------------------------------------- /directmessage.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | type DirectMessage struct { 4 | CreatedAt string `json:"created_at"` 5 | Entities Entities `json:"entities"` 6 | Id int64 `json:"id"` 7 | IdStr string `json:"id_str"` 8 | Recipient User `json:"recipient"` 9 | RecipientId int64 `json:"recipient_id"` 10 | RecipientScreenName string `json:"recipient_screen_name"` 11 | Sender User `json:"sender"` 12 | SenderId int64 `json:"sender_id"` 13 | SenderScreenName string `json:"sender_screen_name"` 14 | Text string `json:"text"` 15 | } 16 | -------------------------------------------------------------------------------- /directmessages.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | ) 7 | 8 | func (a TwitterApi) GetDirectMessages(v url.Values) (messages []DirectMessage, err error) { 9 | response_ch := make(chan response) 10 | a.queryQueue <- query{a.baseUrl + "/direct_messages.json", v, &messages, _GET, response_ch} 11 | return messages, (<-response_ch).err 12 | } 13 | 14 | func (a TwitterApi) GetDirectMessagesSent(v url.Values) (messages []DirectMessage, err error) { 15 | response_ch := make(chan response) 16 | a.queryQueue <- query{a.baseUrl + "/direct_messages/sent.json", v, &messages, _GET, response_ch} 17 | return messages, (<-response_ch).err 18 | } 19 | 20 | func (a TwitterApi) GetDirectMessagesShow(v url.Values) (message DirectMessage, err error) { 21 | response_ch := make(chan response) 22 | a.queryQueue <- query{a.baseUrl + "/direct_messages/show.json", v, &message, _GET, response_ch} 23 | return message, (<-response_ch).err 24 | } 25 | 26 | // https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/new-message 27 | func (a TwitterApi) PostDMToScreenName(text, screenName string) (message DirectMessage, err error) { 28 | v := url.Values{} 29 | v.Set("screen_name", screenName) 30 | v.Set("text", text) 31 | return a.postDirectMessagesImpl(v) 32 | } 33 | 34 | // https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/new-message 35 | func (a TwitterApi) PostDMToUserId(text string, userId int64) (message DirectMessage, err error) { 36 | v := url.Values{} 37 | v.Set("user_id", strconv.FormatInt(userId, 10)) 38 | v.Set("text", text) 39 | return a.postDirectMessagesImpl(v) 40 | } 41 | 42 | // DeleteDirectMessage will destroy (delete) the direct message with the specified ID. 43 | // https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/delete-message 44 | func (a TwitterApi) DeleteDirectMessage(id int64, includeEntities bool) (message DirectMessage, err error) { 45 | v := url.Values{} 46 | v.Set("id", strconv.FormatInt(id, 10)) 47 | v.Set("include_entities", strconv.FormatBool(includeEntities)) 48 | response_ch := make(chan response) 49 | a.queryQueue <- query{a.baseUrl + "/direct_messages/destroy.json", v, &message, _POST, response_ch} 50 | return message, (<-response_ch).err 51 | } 52 | 53 | func (a TwitterApi) postDirectMessagesImpl(v url.Values) (message DirectMessage, err error) { 54 | response_ch := make(chan response) 55 | a.queryQueue <- query{a.baseUrl + "/direct_messages/new.json", v, &message, _POST, response_ch} 56 | return message, (<-response_ch).err 57 | } 58 | 59 | // IndicateTyping will create a typing indicator 60 | // https://developer.twitter.com/en/docs/direct-messages/typing-indicator-and-read-receipts/api-reference/new-typing-indicator 61 | func (a TwitterApi) IndicateTyping(id int64) (err error) { 62 | v := url.Values{} 63 | v.Set("recipient_id", strconv.FormatInt(id, 10)) 64 | response_ch := make(chan response) 65 | a.queryQueue <- query{a.baseUrl + "/direct_messages/indicate_typing.json", v, nil, _POST, response_ch} 66 | return (<-response_ch).err 67 | } 68 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "net/url" 9 | "strconv" 10 | "time" 11 | ) 12 | 13 | const ( 14 | //Error code defintions match the Twitter documentation 15 | //https://developer.twitter.com/en/docs/basics/response-codes 16 | TwitterErrorCouldNotAuthenticate = 32 17 | TwitterErrorDoesNotExist = 34 18 | TwitterErrorAccountSuspended = 64 19 | TwitterErrorApi1Deprecation = 68 //This should never be needed 20 | TwitterErrorRateLimitExceeded = 88 21 | TwitterErrorInvalidToken = 89 22 | TwitterErrorOverCapacity = 130 23 | TwitterErrorInternalError = 131 24 | TwitterErrorCouldNotAuthenticateYou = 135 25 | TwitterErrorStatusIsADuplicate = 187 26 | TwitterErrorBadAuthenticationData = 215 27 | TwitterErrorUserMustVerifyLogin = 231 28 | 29 | // Undocumented by Twitter, but may be returned instead of 34 30 | TwitterErrorDoesNotExist2 = 144 31 | ) 32 | 33 | type ApiError struct { 34 | StatusCode int 35 | Header http.Header 36 | Body string 37 | Decoded TwitterErrorResponse 38 | URL *url.URL 39 | } 40 | 41 | func newApiError(resp *http.Response) *ApiError { 42 | // TODO don't ignore this error 43 | // TODO don't use ReadAll 44 | p, _ := ioutil.ReadAll(resp.Body) 45 | 46 | var twitterErrorResp TwitterErrorResponse 47 | _ = json.Unmarshal(p, &twitterErrorResp) 48 | return &ApiError{ 49 | StatusCode: resp.StatusCode, 50 | Header: resp.Header, 51 | Body: string(p), 52 | Decoded: twitterErrorResp, 53 | URL: resp.Request.URL, 54 | } 55 | } 56 | 57 | // ApiError supports the error interface 58 | func (aerr ApiError) Error() string { 59 | return fmt.Sprintf("Get %s returned status %d, %s", aerr.URL, aerr.StatusCode, aerr.Body) 60 | } 61 | 62 | // Check to see if an error is a Rate Limiting error. If so, find the next available window in the header. 63 | // Use like so: 64 | // 65 | // if aerr, ok := err.(*ApiError); ok { 66 | // if isRateLimitError, nextWindow := aerr.RateLimitCheck(); isRateLimitError { 67 | // <-time.After(nextWindow.Sub(time.Now())) 68 | // } 69 | // } 70 | // 71 | func (aerr *ApiError) RateLimitCheck() (isRateLimitError bool, nextWindow time.Time) { 72 | // TODO check for error code 130, which also signifies a rate limit 73 | if aerr.StatusCode == 429 { 74 | if reset := aerr.Header.Get("X-Rate-Limit-Reset"); reset != "" { 75 | if resetUnix, err := strconv.ParseInt(reset, 10, 64); err == nil { 76 | resetTime := time.Unix(resetUnix, 0) 77 | // Reject any time greater than an hour away 78 | if resetTime.Sub(time.Now()) > time.Hour { 79 | return true, time.Now().Add(15 * time.Minute) 80 | } 81 | 82 | return true, resetTime 83 | } 84 | } 85 | } 86 | 87 | return false, time.Time{} 88 | } 89 | 90 | //TwitterErrorResponse has an array of Twitter error messages 91 | //It satisfies the "error" interface 92 | //For the most part, Twitter seems to return only a single error message 93 | //Currently, we assume that this always contains exactly one error message 94 | type TwitterErrorResponse struct { 95 | Errors []TwitterError `json:"errors"` 96 | } 97 | 98 | func (tr TwitterErrorResponse) First() error { 99 | return tr.Errors[0] 100 | } 101 | 102 | func (tr TwitterErrorResponse) Error() string { 103 | return tr.Errors[0].Message 104 | } 105 | 106 | //TwitterError represents a single Twitter error messages/code pair 107 | type TwitterError struct { 108 | Message string `json:"message"` 109 | Code int `json:"code"` 110 | } 111 | 112 | func (te TwitterError) Error() string { 113 | return te.Message 114 | } 115 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package anaconda_test 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/ChimeraCoder/anaconda" 8 | ) 9 | 10 | // Initialize an client library for a given user. 11 | // This only needs to be done *once* per user 12 | func ExampleTwitterApi_InitializeClient() { 13 | api := anaconda.NewTwitterApiWithCredentials(ACCESS_TOKEN, ACCESS_TOKEN_SECRET, "your-consumer-key", "your-consumer-secret") 14 | fmt.Println(*api.Credentials) 15 | } 16 | 17 | func ExampleTwitterApi_GetSearch() { 18 | anaconda.SetConsumerKey("your-consumer-key") 19 | anaconda.SetConsumerSecret("your-consumer-secret") 20 | api := anaconda.NewTwitterApi("your-access-token", "your-access-token-secret") 21 | search_result, err := api.GetSearch("golang", nil) 22 | if err != nil { 23 | panic(err) 24 | } 25 | for _, tweet := range search_result.Statuses { 26 | fmt.Print(tweet.Text) 27 | } 28 | } 29 | 30 | // Throttling queries can easily be handled in the background, automatically 31 | func ExampleTwitterApi_Throttling() { 32 | api := anaconda.NewTwitterApi("your-access-token", "your-access-token-secret") 33 | api.EnableThrottling(10*time.Second, 5) 34 | 35 | // These queries will execute in order 36 | // with appropriate delays inserted only if necessary 37 | golangTweets, err := api.GetSearch("golang", nil) 38 | anacondaTweets, err2 := api.GetSearch("anaconda", nil) 39 | 40 | if err != nil { 41 | panic(err) 42 | } 43 | if err2 != nil { 44 | panic(err) 45 | } 46 | 47 | fmt.Println(golangTweets) 48 | fmt.Println(anacondaTweets) 49 | } 50 | 51 | // Fetch a list of all followers without any need for managing cursors 52 | // (Each page is automatically fetched when the previous one is read) 53 | func ExampleTwitterApi_GetFollowersListAll() { 54 | pages := api.GetFollowersListAll(nil) 55 | for page := range pages { 56 | //Print the current page of followers 57 | fmt.Println(page.Followers) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /favorites.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | func (a TwitterApi) GetFavorites(v url.Values) (favorites []Tweet, err error) { 8 | response_ch := make(chan response) 9 | a.queryQueue <- query{a.baseUrl + "/favorites/list.json", v, &favorites, _GET, response_ch} 10 | return favorites, (<-response_ch).err 11 | } 12 | -------------------------------------------------------------------------------- /geosearch.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import "net/url" 4 | 5 | type GeoSearchResult struct { 6 | Result struct { 7 | Places []struct { 8 | ID string `json:"id"` 9 | URL string `json:"url"` 10 | PlaceType string `json:"place_type"` 11 | Name string `json:"name"` 12 | FullName string `json:"full_name"` 13 | CountryCode string `json:"country_code"` 14 | Country string `json:"country"` 15 | ContainedWithin []struct { 16 | ID string `json:"id"` 17 | URL string `json:"url"` 18 | PlaceType string `json:"place_type"` 19 | Name string `json:"name"` 20 | FullName string `json:"full_name"` 21 | CountryCode string `json:"country_code"` 22 | Country string `json:"country"` 23 | Centroid []float64 `json:"centroid"` 24 | BoundingBox struct { 25 | Type string `json:"type"` 26 | Coordinates [][][]float64 `json:"coordinates"` 27 | } `json:"bounding_box"` 28 | Attributes struct { 29 | } `json:"attributes"` 30 | } `json:"contained_within"` 31 | Centroid []float64 `json:"centroid"` 32 | BoundingBox struct { 33 | Type string `json:"type"` 34 | Coordinates [][][]float64 `json:"coordinates"` 35 | } `json:"bounding_box"` 36 | Attributes struct { 37 | } `json:"attributes"` 38 | } `json:"places"` 39 | } `json:"result"` 40 | Query struct { 41 | URL string `json:"url"` 42 | Type string `json:"type"` 43 | Params struct { 44 | Accuracy float64 `json:"accuracy"` 45 | Granularity string `json:"granularity"` 46 | Query string `json:"query"` 47 | Autocomplete bool `json:"autocomplete"` 48 | TrimPlace bool `json:"trim_place"` 49 | } `json:"params"` 50 | } `json:"query"` 51 | } 52 | 53 | func (a TwitterApi) GeoSearch(v url.Values) (c GeoSearchResult, err error) { 54 | response_ch := make(chan response) 55 | a.queryQueue <- query{a.baseUrl + "/geo/search.json", v, &c, _GET, response_ch} 56 | return c, (<-response_ch).err 57 | } 58 | -------------------------------------------------------------------------------- /json/account/verify_credentials.json: -------------------------------------------------------------------------------- 1 | {"id":182675886,"id_str":"182675886","name":"Aditya Mukerjee","screen_name":"chimeracoder","location":"New York, NY","description":"Risk engineer at @stripe. Linux dev, statistician. Writing lots of Go. Alum of @recursecenter, @cornell_tech, @columbia","url":"http:\/\/t.co\/YhPyE6aJso","entities":{"url":{"urls":[{"url":"http:\/\/t.co\/YhPyE6aJso","expanded_url":"http:\/\/www.adityamukerjee.net","display_url":"adityamukerjee.net","indices":[0,22]}]},"description":{"urls":[]}},"protected":false,"followers_count":2872,"friends_count":769,"listed_count":160,"created_at":"Wed Aug 25 03:49:41 +0000 2010","favourites_count":2814,"utc_offset":-18000,"time_zone":"Eastern Time (US & Canada)","geo_enabled":false,"verified":false,"statuses_count":7798,"lang":"en","status":{"created_at":"Sat Nov 14 17:17:43 +0000 2015","id":665579376570998785,"id_str":"665579376570998785","text":"@ztsamudzi @SubMedina it's kind of sickening how we ignore ongoing violence until the victims become white.","source":"\u003ca href=\"http:\/\/twitter.com\" rel=\"nofollow\"\u003eTwitter Web Client\u003c\/a\u003e","truncated":false,"in_reply_to_status_id":665577102150934528,"in_reply_to_status_id_str":"665577102150934528","in_reply_to_user_id":182675886,"in_reply_to_user_id_str":"182675886","in_reply_to_screen_name":"chimeracoder","geo":null,"coordinates":null,"place":null,"contributors":null,"retweet_count":1,"favorite_count":2,"entities":{"hashtags":[],"symbols":[],"user_mentions":[{"screen_name":"ztsamudzi","name":"Zo\u00e9 S.","id":2968635633,"id_str":"2968635633","indices":[0,10]},{"screen_name":"SubMedina","name":"Emotional Laborer","id":14514703,"id_str":"14514703","indices":[11,21]}],"urls":[]},"favorited":false,"retweeted":false,"lang":"en"},"contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/1807988313\/230348_1870593437981_1035450059_32104665_3285049_n_cropped_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/1807988313\/230348_1870593437981_1035450059_32104665_3285049_n_cropped_normal.jpg","profile_link_color":"0084B4","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":false,"default_profile":true,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false} -------------------------------------------------------------------------------- /json/direct_messages/new.json: -------------------------------------------------------------------------------- 1 | {"id":666024290140217347,"id_str":"666024290140217347","text":"Test the anaconda lib","sender":{"id":182675886,"id_str":"182675886","name":"Aditya Mukerjee","screen_name":"chimeracoder","location":"New York, NY","description":"Risk engineer at @stripe. Linux dev, statistician. Writing lots of Go. Alum of @recursecenter, @cornell_tech, @columbia","url":"http:\/\/t.co\/YhPyE6aJso","entities":{"url":{"urls":[{"url":"http:\/\/t.co\/YhPyE6aJso","expanded_url":"http:\/\/www.adityamukerjee.net","display_url":"adityamukerjee.net","indices":[0,22]}]},"description":{"urls":[]}},"protected":false,"followers_count":2872,"friends_count":769,"listed_count":160,"created_at":"Wed Aug 25 03:49:41 +0000 2010","favourites_count":2814,"utc_offset":-18000,"time_zone":"Eastern Time (US & Canada)","geo_enabled":false,"verified":false,"statuses_count":7798,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/1807988313\/230348_1870593437981_1035450059_32104665_3285049_n_cropped_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/1807988313\/230348_1870593437981_1035450059_32104665_3285049_n_cropped_normal.jpg","profile_link_color":"0084B4","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":false,"default_profile":true,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false},"sender_id":182675886,"sender_id_str":"182675886","sender_screen_name":"chimeracoder","recipient":{"id":182675886,"id_str":"182675886","name":"Aditya Mukerjee","screen_name":"chimeracoder","location":"New York, NY","description":"Risk engineer at @stripe. Linux dev, statistician. Writing lots of Go. Alum of @recursecenter, @cornell_tech, @columbia","url":"http:\/\/t.co\/YhPyE6aJso","entities":{"url":{"urls":[{"url":"http:\/\/t.co\/YhPyE6aJso","expanded_url":"http:\/\/www.adityamukerjee.net","display_url":"adityamukerjee.net","indices":[0,22]}]},"description":{"urls":[]}},"protected":false,"followers_count":2872,"friends_count":769,"listed_count":160,"created_at":"Wed Aug 25 03:49:41 +0000 2010","favourites_count":2814,"utc_offset":-18000,"time_zone":"Eastern Time (US & Canada)","geo_enabled":false,"verified":false,"statuses_count":7798,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/1807988313\/230348_1870593437981_1035450059_32104665_3285049_n_cropped_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/1807988313\/230348_1870593437981_1035450059_32104665_3285049_n_cropped_normal.jpg","profile_link_color":"0084B4","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":false,"default_profile":true,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false},"recipient_id":182675886,"recipient_id_str":"182675886","recipient_screen_name":"chimeracoder","created_at":"Sun Nov 15 22:45:39 +0000 2015","entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]}} -------------------------------------------------------------------------------- /json/lists/members/destroy.json: -------------------------------------------------------------------------------- 1 | {"id":815255107788906496,"id_str":"815255107788906496","name":"Moments","uri":"\/Twitter\/lists\/moments","subscriber_count":87,"member_count":9,"mode":"public","description":"News Tweets from Twitter's Moment curation teams around the globe.","slug":"moments","full_name":"@Twitter\/moments","created_at":"Sat Dec 31 17:55:39 +0000 2016","following":false,"user":{"id":783214,"id_str":"783214","name":"Twitter","screen_name":"Twitter","location":"San Francisco, CA","description":"Your official source for what\u2019s happening. Need a hand? Visit https:\/\/t.co\/heEvRrCFXn","url":"https:\/\/t.co\/gN5JJwhQy7","entities":{"url":{"urls":[{"url":"https:\/\/t.co\/gN5JJwhQy7","expanded_url":"https:\/\/blog.twitter.com\/","display_url":"blog.twitter.com","indices":[0,23]}]},"description":{"urls":[{"url":"https:\/\/t.co\/heEvRrCFXn","expanded_url":"https:\/\/help.twitter.com","display_url":"help.twitter.com","indices":[63,86]}]}},"protected":false,"followers_count":62951172,"friends_count":144,"listed_count":90763,"created_at":"Tue Feb 20 14:35:54 +0000 2007","favourites_count":5555,"utc_offset":-25200,"time_zone":"Pacific Time (US & Canada)","geo_enabled":true,"verified":true,"statuses_count":6694,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"ACDED6","profile_background_image_url":"http:\/\/pbs.twimg.com\/profile_background_images\/657090062\/l1uqey5sy82r9ijhke1i.png","profile_background_image_url_https":"https:\/\/pbs.twimg.com\/profile_background_images\/657090062\/l1uqey5sy82r9ijhke1i.png","profile_background_tile":true,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/875087697177567232\/Qfy0kRIP_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/875087697177567232\/Qfy0kRIP_normal.jpg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/783214\/1520968514","profile_link_color":"1B95E0","profile_sidebar_border_color":"FFFFFF","profile_sidebar_fill_color":"F6F6F6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":true,"default_profile":false,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"regular"}} -------------------------------------------------------------------------------- /json/lists/members/destroy_all.json: -------------------------------------------------------------------------------- 1 | {"id":815255107788906496,"id_str":"815255107788906496","name":"Moments","uri":"\/Twitter\/lists\/moments","subscriber_count":87,"member_count":9,"mode":"public","description":"News Tweets from Twitter's Moment curation teams around the globe.","slug":"moments","full_name":"@Twitter\/moments","created_at":"Sat Dec 31 17:55:39 +0000 2016","following":false,"user":{"id":783214,"id_str":"783214","name":"Twitter","screen_name":"Twitter","location":"San Francisco, CA","description":"Your official source for what\u2019s happening. Need a hand? Visit https:\/\/t.co\/heEvRrCFXn","url":"https:\/\/t.co\/gN5JJwhQy7","entities":{"url":{"urls":[{"url":"https:\/\/t.co\/gN5JJwhQy7","expanded_url":"https:\/\/blog.twitter.com\/","display_url":"blog.twitter.com","indices":[0,23]}]},"description":{"urls":[{"url":"https:\/\/t.co\/heEvRrCFXn","expanded_url":"https:\/\/help.twitter.com","display_url":"help.twitter.com","indices":[63,86]}]}},"protected":false,"followers_count":62951172,"friends_count":144,"listed_count":90763,"created_at":"Tue Feb 20 14:35:54 +0000 2007","favourites_count":5555,"utc_offset":-25200,"time_zone":"Pacific Time (US & Canada)","geo_enabled":true,"verified":true,"statuses_count":6694,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"ACDED6","profile_background_image_url":"http:\/\/pbs.twimg.com\/profile_background_images\/657090062\/l1uqey5sy82r9ijhke1i.png","profile_background_image_url_https":"https:\/\/pbs.twimg.com\/profile_background_images\/657090062\/l1uqey5sy82r9ijhke1i.png","profile_background_tile":true,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/875087697177567232\/Qfy0kRIP_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/875087697177567232\/Qfy0kRIP_normal.jpg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/783214\/1520968514","profile_link_color":"1B95E0","profile_sidebar_border_color":"FFFFFF","profile_sidebar_fill_color":"F6F6F6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":true,"default_profile":false,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"regular"}} -------------------------------------------------------------------------------- /json/statuses/oembed.json: -------------------------------------------------------------------------------- 1 | {"url":"https:\/\/twitter.com\/twitter\/status\/99530515043983360","author_name":"Twitter","author_url":"https:\/\/twitter.com\/twitter","html":"\u003Cblockquote class=\"twitter-tweet\"\u003E\u003Cp lang=\"en\" dir=\"ltr\"\u003ECool! “\u003Ca href=\"https:\/\/twitter.com\/tw1tt3rart\"\u003E@tw1tt3rart\u003C\/a\u003E: \u003Ca href=\"https:\/\/twitter.com\/hashtag\/TWITTERART?src=hash\"\u003E#TWITTERART\u003C\/a\u003E ╱╱╱╱╱╱╱╱ ╱╱╭━━━━╮╱╱╭━━━━╮ ╱╱┃▇┆┆▇┃╱╭┫ⓦⓔⓔⓚ┃ ╱╱┃▽▽▽▽┃━╯┃♡ⓔⓝⓓ┃ ╱╭┫△△△△┣╮╱╰━━━━╯ ╱┃┃┆┆┆┆┃┃╱╱╱╱╱╱ ╱┗┫┆┏┓┆┣┛╱╱╱╱╱”\u003C\/p\u003E— Twitter (@twitter) \u003Ca href=\"https:\/\/twitter.com\/twitter\/status\/99530515043983360\"\u003EAugust 5, 2011\u003C\/a\u003E\u003C\/blockquote\u003E\n\u003Cscript async src=\"\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"\u003E\u003C\/script\u003E","width":550,"height":null,"type":"rich","cache_age":"3153600000","provider_name":"Twitter","provider_url":"https:\/\/twitter.com","version":"1.0"}1\u2571\u201D\u003C\/p\u003E— Twitter (@twitter) \u003Ca href=\"https:\/\/twitter.com\/twitter\/status\/99530515043983360\"\u003EAugust 5, 2011\u003C\/a\u003E\u003C\/blockquote\u003E\n\u003Cscript async src=\"\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"\u003E\u003C\/script\u003E","width":550} -------------------------------------------------------------------------------- /json/statuses/show.json: -------------------------------------------------------------------------------- 1 | {"created_at":"Tue Feb 19 08:04:41 +0000 2013","id":303777106620452864,"id_str":"303777106620452864","full_text":"golang-syd is in session. Dave Symonds is now talking about API design and protobufs. #golang http:\/\/t.co\/eSq3ROwu","truncated":false,"entities":{"hashtags":[{"text":"golang","indices":[86,93]}],"symbols":[],"user_mentions":[],"urls":[],"media":[{"id":303777106628841472,"id_str":"303777106628841472","indices":[94,114],"media_url":"http:\/\/pbs.twimg.com\/media\/BDc7q0OCEAAoe2C.jpg","media_url_https":"https:\/\/pbs.twimg.com\/media\/BDc7q0OCEAAoe2C.jpg","url":"http:\/\/t.co\/eSq3ROwu","display_url":"pic.twitter.com\/eSq3ROwu","expanded_url":"http:\/\/twitter.com\/go_nuts\/status\/303777106620452864\/photo\/1","type":"photo","sizes":{"small":{"w":340,"h":255,"resize":"fit"},"medium":{"w":600,"h":450,"resize":"fit"},"thumb":{"w":150,"h":150,"resize":"crop"},"large":{"w":1024,"h":768,"resize":"fit"}}}]},"extended_entities":{"media":[{"id":303777106628841472,"id_str":"303777106628841472","indices":[94,114],"media_url":"http:\/\/pbs.twimg.com\/media\/BDc7q0OCEAAoe2C.jpg","media_url_https":"https:\/\/pbs.twimg.com\/media\/BDc7q0OCEAAoe2C.jpg","url":"http:\/\/t.co\/eSq3ROwu","display_url":"pic.twitter.com\/eSq3ROwu","expanded_url":"http:\/\/twitter.com\/go_nuts\/status\/303777106620452864\/photo\/1","type":"photo","sizes":{"small":{"w":340,"h":255,"resize":"fit"},"medium":{"w":600,"h":450,"resize":"fit"},"thumb":{"w":150,"h":150,"resize":"crop"},"large":{"w":1024,"h":768,"resize":"fit"}}}]},"source":"\u003ca href=\"http:\/\/twitter.com\/download\/android\" rel=\"nofollow\"\u003eTwitter for Android\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":113419064,"id_str":"113419064","name":"Go","screen_name":"golang","location":"","description":"Go will make you love programming again. I promise.","url":"http:\/\/t.co\/C4svVTkUmj","entities":{"url":{"urls":[{"url":"http:\/\/t.co\/C4svVTkUmj","expanded_url":"http:\/\/golang.org\/","display_url":"golang.org","indices":[0,22]}]},"description":{"urls":[]}},"protected":false,"followers_count":42195,"friends_count":0,"listed_count":1115,"created_at":"Thu Feb 11 18:04:38 +0000 2010","favourites_count":211,"utc_offset":-28800,"time_zone":"Alaska","geo_enabled":false,"verified":false,"statuses_count":1954,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/2388595262\/v02jhlxou71qagr6mwet_normal.png","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/2388595262\/v02jhlxou71qagr6mwet_normal.png","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/113419064\/1398369112","profile_link_color":"0084B4","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":false,"default_profile":true,"default_profile_image":false,"following":true,"follow_request_sent":false,"notifications":false},"geo":null,"coordinates":null,"place":null,"contributors":null,"is_quote_status":false,"retweet_count":2,"favorite_count":3,"favorited":false,"retweeted":false,"possibly_sensitive":false,"possibly_sensitive_appealable":false,"lang":"en"} -------------------------------------------------------------------------------- /json/statuses/show.json_id_404409873170841600_tweet_mode_extended: -------------------------------------------------------------------------------- 1 | {"errors":[{"code":144,"message":"No status found with that ID."}]} -------------------------------------------------------------------------------- /json/statuses/show.json_id_738567564641599489_tweet_mode_extended: -------------------------------------------------------------------------------- 1 | {"created_at":"Fri Jun 03 03:06:43 +0000 2016","id":738567564641599489,"id_str":"738567564641599489","full_text":"Well, this has certainly come a long way! https:\/\/t.co\/QomzRzwcti","truncated":false,"entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[{"url":"https:\/\/t.co\/QomzRzwcti","expanded_url":"https:\/\/twitter.com\/chimeracoder\/status\/284377451625340928","display_url":"twitter.com\/chimeracoder\/s\u2026","indices":[42,65]}]},"source":"\u003ca href=\"http:\/\/twitter.com\" rel=\"nofollow\"\u003eTwitter Web Client\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":182675886,"id_str":"182675886","name":"Aditya Mukerjee","screen_name":"chimeracoder","location":"New York, NY","description":"Risk engineer at @stripe. Linux dev, statistician. Writing lots of Go. Alum of @recursecenter, @cornell_tech, @columbia","url":"https:\/\/t.co\/6PwvHXBN5Y","entities":{"url":{"urls":[{"url":"https:\/\/t.co\/6PwvHXBN5Y","expanded_url":"https:\/\/adityamukerjee.net","display_url":"adityamukerjee.net","indices":[0,23]}]},"description":{"urls":[]}},"protected":false,"followers_count":3061,"friends_count":914,"listed_count":172,"created_at":"Wed Aug 25 03:49:41 +0000 2010","favourites_count":5193,"utc_offset":-14400,"time_zone":"Eastern Time (US & Canada)","geo_enabled":false,"verified":false,"statuses_count":9559,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/1807988313\/230348_1870593437981_1035450059_32104665_3285049_n_cropped_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/1807988313\/230348_1870593437981_1035450059_32104665_3285049_n_cropped_normal.jpg","profile_link_color":"0084B4","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":false,"default_profile":true,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false},"geo":null,"coordinates":null,"place":null,"contributors":null,"quoted_status_id":284377451625340928,"quoted_status_id_str":"284377451625340928","quoted_status":{"created_at":"Thu Dec 27 19:17:22 +0000 2012","id":284377451625340928,"id_str":"284377451625340928","full_text":"Just created gojson - a simple tool for turning JSON data into Go structs! http:\/\/t.co\/QM6k9AUV #golang","truncated":false,"entities":{"hashtags":[{"text":"golang","indices":[96,103]}],"symbols":[],"user_mentions":[],"urls":[{"url":"http:\/\/t.co\/QM6k9AUV","expanded_url":"http:\/\/bit.ly\/UbfXOC","display_url":"bit.ly\/UbfXOC","indices":[75,95]}]},"source":"\u003ca href=\"http:\/\/twitter.com\" rel=\"nofollow\"\u003eTwitter Web Client\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":182675886,"id_str":"182675886","name":"Aditya Mukerjee","screen_name":"chimeracoder","location":"New York, NY","description":"Risk engineer at @stripe. Linux dev, statistician. Writing lots of Go. Alum of @recursecenter, @cornell_tech, @columbia","url":"https:\/\/t.co\/6PwvHXBN5Y","entities":{"url":{"urls":[{"url":"https:\/\/t.co\/6PwvHXBN5Y","expanded_url":"https:\/\/adityamukerjee.net","display_url":"adityamukerjee.net","indices":[0,23]}]},"description":{"urls":[]}},"protected":false,"followers_count":3061,"friends_count":914,"listed_count":172,"created_at":"Wed Aug 25 03:49:41 +0000 2010","favourites_count":5193,"utc_offset":-14400,"time_zone":"Eastern Time (US & Canada)","geo_enabled":false,"verified":false,"statuses_count":9559,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/1807988313\/230348_1870593437981_1035450059_32104665_3285049_n_cropped_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/1807988313\/230348_1870593437981_1035450059_32104665_3285049_n_cropped_normal.jpg","profile_link_color":"0084B4","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":false,"default_profile":true,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false},"geo":null,"coordinates":null,"place":null,"contributors":null,"is_quote_status":false,"retweet_count":0,"favorite_count":2,"favorited":false,"retweeted":false,"possibly_sensitive":false,"possibly_sensitive_appealable":false,"lang":"en"},"is_quote_status":true,"retweet_count":0,"favorite_count":1,"favorited":false,"retweeted":false,"possibly_sensitive":false,"possibly_sensitive_appealable":false,"lang":"en"} -------------------------------------------------------------------------------- /json/users/lookup.json: -------------------------------------------------------------------------------- 1 | [{"id":182675886,"id_str":"182675886","name":"Aditya Mukerjee","screen_name":"chimeracoder","location":"New York, NY","description":"Risk engineer at @stripe. Linux dev, statistician. Writing lots of Go. Alum of @recursecenter, @cornell_tech, @columbia","url":"http:\/\/t.co\/YhPyE6aJso","entities":{"url":{"urls":[{"url":"http:\/\/t.co\/YhPyE6aJso","expanded_url":"http:\/\/www.adityamukerjee.net","display_url":"adityamukerjee.net","indices":[0,22]}]},"description":{"urls":[]}},"protected":false,"followers_count":2872,"friends_count":769,"listed_count":160,"created_at":"Wed Aug 25 03:49:41 +0000 2010","favourites_count":2814,"utc_offset":-18000,"time_zone":"Eastern Time (US & Canada)","geo_enabled":false,"verified":false,"statuses_count":7798,"lang":"en","status":{"created_at":"Sat Nov 14 17:17:43 +0000 2015","id":665579376570998785,"id_str":"665579376570998785","text":"@ztsamudzi @SubMedina it's kind of sickening how we ignore ongoing violence until the victims become white.","source":"\u003ca href=\"http:\/\/twitter.com\" rel=\"nofollow\"\u003eTwitter Web Client\u003c\/a\u003e","truncated":false,"in_reply_to_status_id":665577102150934528,"in_reply_to_status_id_str":"665577102150934528","in_reply_to_user_id":182675886,"in_reply_to_user_id_str":"182675886","in_reply_to_screen_name":"chimeracoder","geo":null,"coordinates":null,"place":null,"contributors":null,"retweet_count":1,"favorite_count":2,"entities":{"hashtags":[],"symbols":[],"user_mentions":[{"screen_name":"ztsamudzi","name":"Zo\u00e9 S.","id":2968635633,"id_str":"2968635633","indices":[0,10]},{"screen_name":"SubMedina","name":"Emotional Laborer","id":14514703,"id_str":"14514703","indices":[11,21]}],"urls":[]},"favorited":false,"retweeted":false,"lang":"en"},"contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/1807988313\/230348_1870593437981_1035450059_32104665_3285049_n_cropped_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/1807988313\/230348_1870593437981_1035450059_32104665_3285049_n_cropped_normal.jpg","profile_link_color":"0084B4","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":false,"default_profile":true,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false}] -------------------------------------------------------------------------------- /list.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | type ListResponse struct { 4 | PreviousCursor int `json:"previous_cursor"` 5 | NextCursor int `json:"next_cursor"` 6 | Lists []List `json:"lists"` 7 | } 8 | 9 | type AddUserToListResponse struct { 10 | Users []User `json:"users"` 11 | } 12 | 13 | type List struct { 14 | Slug string `json:"slug"` 15 | Name string `json:"name"` 16 | URL string `json:"uri"` 17 | CreatedAt string `json:"created_at"` 18 | Id int64 `json:"id"` 19 | SubscriberCount int64 `json:"subscriber_count"` 20 | MemberCount int64 `json:"member_count"` 21 | Mode string `json:"mode"` 22 | FullName string `json:"full_name"` 23 | Description string `json:"description"` 24 | User User `json:"user"` 25 | Following bool `json:"following"` 26 | } 27 | -------------------------------------------------------------------------------- /lists.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // CreateList implements /lists/create.json 10 | func (a TwitterApi) CreateList(name, description string, v url.Values) (list List, err error) { 11 | v = cleanValues(v) 12 | v.Set("name", name) 13 | v.Set("description", description) 14 | 15 | response_ch := make(chan response) 16 | a.queryQueue <- query{a.baseUrl + "/lists/create.json", v, &list, _POST, response_ch} 17 | return list, (<-response_ch).err 18 | } 19 | 20 | // AddUserToList implements /lists/members/create.json 21 | func (a TwitterApi) AddUserToList(screenName string, listID int64, v url.Values) (users []User, err error) { 22 | v = cleanValues(v) 23 | v.Set("list_id", strconv.FormatInt(listID, 10)) 24 | v.Set("screen_name", screenName) 25 | 26 | var addUserToListResponse AddUserToListResponse 27 | 28 | response_ch := make(chan response) 29 | a.queryQueue <- query{a.baseUrl + "/lists/members/create.json", v, &addUserToListResponse, _POST, response_ch} 30 | return addUserToListResponse.Users, (<-response_ch).err 31 | } 32 | 33 | // AddMultipleUsersToList implements /lists/members/create_all.json 34 | func (a TwitterApi) AddMultipleUsersToList(screenNames []string, listID int64, v url.Values) (list List, err error) { 35 | v = cleanValues(v) 36 | v.Set("list_id", strconv.FormatInt(listID, 10)) 37 | v.Set("screen_name", strings.Join(screenNames, ",")) 38 | 39 | response_ch := make(chan response) 40 | a.queryQueue <- query{a.baseUrl + "/lists/members/create_all.json", v, &list, _POST, response_ch} 41 | r := <-response_ch 42 | return list, r.err 43 | } 44 | 45 | // RemoveUserFromList implements /lists/members/destroy.json 46 | func (a TwitterApi) RemoveUserFromList(screenName string, listID int64, v url.Values) (list List, err error) { 47 | v = cleanValues(v) 48 | v.Set("list_id", strconv.FormatInt(listID, 10)) 49 | v.Set("screen_name", screenName) 50 | 51 | response_ch := make(chan response) 52 | a.queryQueue <- query{a.baseUrl + "/lists/members/destroy.json", v, &list, _POST, response_ch} 53 | r := <-response_ch 54 | return list, r.err 55 | } 56 | 57 | // RemoveMultipleUsersFromList implements /lists/members/destroy_all.json 58 | func (a TwitterApi) RemoveMultipleUsersFromList(screenNames []string, listID int64, v url.Values) (list List, err error) { 59 | v = cleanValues(v) 60 | v.Set("list_id", strconv.FormatInt(listID, 10)) 61 | v.Set("screen_name", strings.Join(screenNames, ",")) 62 | 63 | response_ch := make(chan response) 64 | a.queryQueue <- query{a.baseUrl + "/lists/members/destroy_all.json", v, &list, _POST, response_ch} 65 | r := <-response_ch 66 | return list, r.err 67 | } 68 | 69 | // GetListsOwnedBy implements /lists/ownerships.json 70 | // screen_name, count, and cursor are all optional values 71 | func (a TwitterApi) GetListsOwnedBy(userID int64, v url.Values) (lists []List, err error) { 72 | v = cleanValues(v) 73 | v.Set("user_id", strconv.FormatInt(userID, 10)) 74 | 75 | var listResponse ListResponse 76 | 77 | response_ch := make(chan response) 78 | a.queryQueue <- query{a.baseUrl + "/lists/ownerships.json", v, &listResponse, _GET, response_ch} 79 | return listResponse.Lists, (<-response_ch).err 80 | } 81 | 82 | func (a TwitterApi) GetListTweets(listID int64, includeRTs bool, v url.Values) (tweets []Tweet, err error) { 83 | v = cleanValues(v) 84 | v.Set("list_id", strconv.FormatInt(listID, 10)) 85 | v.Set("include_rts", strconv.FormatBool(includeRTs)) 86 | 87 | response_ch := make(chan response) 88 | a.queryQueue <- query{a.baseUrl + "/lists/statuses.json", v, &tweets, _GET, response_ch} 89 | return tweets, (<-response_ch).err 90 | } 91 | 92 | // GetList implements /lists/show.json 93 | func (a TwitterApi) GetList(listID int64, v url.Values) (list List, err error) { 94 | v = cleanValues(v) 95 | v.Set("list_id", strconv.FormatInt(listID, 10)) 96 | 97 | response_ch := make(chan response) 98 | a.queryQueue <- query{a.baseUrl + "/lists/show.json", v, &list, _GET, response_ch} 99 | return list, (<-response_ch).err 100 | } 101 | 102 | func (a TwitterApi) GetListTweetsBySlug(slug string, ownerScreenName string, includeRTs bool, v url.Values) (tweets []Tweet, err error) { 103 | v = cleanValues(v) 104 | v.Set("slug", slug) 105 | v.Set("owner_screen_name", ownerScreenName) 106 | v.Set("include_rts", strconv.FormatBool(includeRTs)) 107 | 108 | response_ch := make(chan response) 109 | a.queryQueue <- query{a.baseUrl + "/lists/statuses.json", v, &tweets, _GET, response_ch} 110 | return tweets, (<-response_ch).err 111 | } 112 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "log" 5 | "os" 6 | ) 7 | 8 | // The Logger interface provides optional logging ability for the streaming API. 9 | // It can also be used to log the rate limiting headers if desired. 10 | type Logger interface { 11 | Fatal(args ...interface{}) 12 | Fatalf(format string, args ...interface{}) 13 | 14 | Panic(args ...interface{}) 15 | Panicf(format string, args ...interface{}) 16 | 17 | // Log functions 18 | Critical(args ...interface{}) 19 | Criticalf(format string, args ...interface{}) 20 | 21 | Error(args ...interface{}) 22 | Errorf(format string, args ...interface{}) 23 | 24 | Warning(args ...interface{}) 25 | Warningf(format string, args ...interface{}) 26 | 27 | Notice(args ...interface{}) 28 | Noticef(format string, args ...interface{}) 29 | 30 | Info(args ...interface{}) 31 | Infof(format string, args ...interface{}) 32 | 33 | Debug(args ...interface{}) 34 | Debugf(format string, args ...interface{}) 35 | } 36 | 37 | // SetLogger sets the Logger used by the API client. 38 | // The default logger is silent. BasicLogger will log to STDERR 39 | // using the log package from the standard library. 40 | func (c *TwitterApi) SetLogger(l Logger) { 41 | c.Log = l 42 | } 43 | 44 | type silentLogger struct { 45 | } 46 | 47 | func (_ silentLogger) Fatal(_ ...interface{}) {} 48 | func (_ silentLogger) Fatalf(_ string, _ ...interface{}) {} 49 | func (_ silentLogger) Panic(_ ...interface{}) {} 50 | func (_ silentLogger) Panicf(_ string, _ ...interface{}) {} 51 | func (_ silentLogger) Critical(_ ...interface{}) {} 52 | func (_ silentLogger) Criticalf(_ string, _ ...interface{}) {} 53 | func (_ silentLogger) Error(_ ...interface{}) {} 54 | func (_ silentLogger) Errorf(_ string, _ ...interface{}) {} 55 | func (_ silentLogger) Warning(_ ...interface{}) {} 56 | func (_ silentLogger) Warningf(_ string, _ ...interface{}) {} 57 | func (_ silentLogger) Notice(_ ...interface{}) {} 58 | func (_ silentLogger) Noticef(_ string, _ ...interface{}) {} 59 | func (_ silentLogger) Info(_ ...interface{}) {} 60 | func (_ silentLogger) Infof(_ string, _ ...interface{}) {} 61 | func (_ silentLogger) Debug(_ ...interface{}) {} 62 | func (_ silentLogger) Debugf(format string, _ ...interface{}) {} 63 | 64 | // BasicLogger is the equivalent of using log from the standard 65 | // library to print to STDERR. 66 | var BasicLogger Logger 67 | 68 | type basicLogger struct { 69 | log *log.Logger //func New(out io.Writer, prefix string, flag int) *Logger 70 | } 71 | 72 | func init() { 73 | BasicLogger = &basicLogger{log: log.New(os.Stderr, log.Prefix(), log.LstdFlags)} 74 | } 75 | 76 | func (l basicLogger) Fatal(items ...interface{}) { l.log.Fatal(items...) } 77 | func (l basicLogger) Fatalf(s string, items ...interface{}) { l.log.Fatalf(s, items...) } 78 | func (l basicLogger) Panic(items ...interface{}) { l.log.Panic(items...) } 79 | func (l basicLogger) Panicf(s string, items ...interface{}) { l.log.Panicf(s, items...) } 80 | func (l basicLogger) Critical(items ...interface{}) { l.log.Print(items...) } 81 | func (l basicLogger) Criticalf(s string, items ...interface{}) { l.log.Printf(s, items...) } 82 | func (l basicLogger) Error(items ...interface{}) { l.log.Print(items...) } 83 | func (l basicLogger) Errorf(s string, items ...interface{}) { l.log.Printf(s, items...) } 84 | func (l basicLogger) Warning(items ...interface{}) { l.log.Print(items...) } 85 | func (l basicLogger) Warningf(s string, items ...interface{}) { l.log.Printf(s, items...) } 86 | func (l basicLogger) Notice(items ...interface{}) { l.log.Print(items...) } 87 | func (l basicLogger) Noticef(s string, items ...interface{}) { l.log.Printf(s, items...) } 88 | func (l basicLogger) Info(items ...interface{}) { l.log.Print(items...) } 89 | func (l basicLogger) Infof(s string, items ...interface{}) { l.log.Printf(s, items...) } 90 | func (l basicLogger) Debug(items ...interface{}) { l.log.Print(items...) } 91 | func (l basicLogger) Debugf(s string, items ...interface{}) { l.log.Printf(s, items...) } 92 | -------------------------------------------------------------------------------- /media.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | ) 7 | 8 | type Media struct { 9 | MediaID int64 `json:"media_id"` 10 | MediaIDString string `json:"media_id_string"` 11 | Size int `json:"size"` 12 | Image Image `json:"image"` 13 | } 14 | 15 | type Image struct { 16 | W int `json:"w"` 17 | H int `json:"h"` 18 | ImageType string `json:"image_type"` 19 | } 20 | 21 | type ChunkedMedia struct { 22 | MediaID int64 `json:"media_id"` 23 | MediaIDString string `json:"media_id_string"` 24 | ExpiresAfterSecs int `json:"expires_after_secs"` 25 | } 26 | 27 | type Video struct { 28 | VideoType string `json:"video_type"` 29 | } 30 | 31 | type VideoMedia struct { 32 | MediaID int64 `json:"media_id"` 33 | MediaIDString string `json:"media_id_string"` 34 | Size int `json:"size"` 35 | ExpiresAfterSecs int `json:"expires_after_secs"` 36 | Video Video `json:"video"` 37 | } 38 | 39 | func (a TwitterApi) UploadMedia(base64String string) (media Media, err error) { 40 | v := url.Values{} 41 | v.Set("media_data", base64String) 42 | 43 | var mediaResponse Media 44 | 45 | response_ch := make(chan response) 46 | a.queryQueue <- query{UploadBaseUrl + "/media/upload.json", v, &mediaResponse, _POST, response_ch} 47 | return mediaResponse, (<-response_ch).err 48 | } 49 | 50 | func (a TwitterApi) UploadVideoInit(totalBytes int, mimeType string) (chunkedMedia ChunkedMedia, err error) { 51 | v := url.Values{} 52 | v.Set("command", "INIT") 53 | v.Set("media_type", mimeType) 54 | v.Set("total_bytes", strconv.FormatInt(int64(totalBytes), 10)) 55 | 56 | var mediaResponse ChunkedMedia 57 | 58 | response_ch := make(chan response) 59 | a.queryQueue <- query{UploadBaseUrl + "/media/upload.json", v, &mediaResponse, _POST, response_ch} 60 | return mediaResponse, (<-response_ch).err 61 | } 62 | 63 | func (a TwitterApi) UploadVideoAppend(mediaIdString string, 64 | segmentIndex int, base64String string) error { 65 | 66 | v := url.Values{} 67 | v.Set("command", "APPEND") 68 | v.Set("media_id", mediaIdString) 69 | v.Set("media_data", base64String) 70 | v.Set("segment_index", strconv.FormatInt(int64(segmentIndex), 10)) 71 | 72 | var emptyResponse interface{} 73 | 74 | response_ch := make(chan response) 75 | a.queryQueue <- query{UploadBaseUrl + "/media/upload.json", v, &emptyResponse, _POST, response_ch} 76 | return (<-response_ch).err 77 | } 78 | 79 | func (a TwitterApi) UploadVideoFinalize(mediaIdString string) (videoMedia VideoMedia, err error) { 80 | v := url.Values{} 81 | v.Set("command", "FINALIZE") 82 | v.Set("media_id", mediaIdString) 83 | 84 | var mediaResponse VideoMedia 85 | 86 | response_ch := make(chan response) 87 | a.queryQueue <- query{UploadBaseUrl + "/media/upload.json", v, &mediaResponse, _POST, response_ch} 88 | return mediaResponse, (<-response_ch).err 89 | } 90 | -------------------------------------------------------------------------------- /mutes.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | ) 7 | 8 | func (a TwitterApi) GetMutedUsersList(v url.Values) (c UserCursor, err error) { 9 | response_ch := make(chan response) 10 | a.queryQueue <- query{a.baseUrl + "/mutes/users/list.json", v, &c, _GET, response_ch} 11 | return c, (<-response_ch).err 12 | } 13 | 14 | func (a TwitterApi) GetMutedUsersIds(v url.Values) (c Cursor, err error) { 15 | response_ch := make(chan response) 16 | a.queryQueue <- query{a.baseUrl + "/mutes/users/ids.json", v, &c, _GET, response_ch} 17 | return c, (<-response_ch).err 18 | } 19 | 20 | func (a TwitterApi) MuteUser(screenName string, v url.Values) (user User, err error) { 21 | v = cleanValues(v) 22 | v.Set("screen_name", screenName) 23 | return a.Mute(v) 24 | } 25 | 26 | func (a TwitterApi) MuteUserId(id int64, v url.Values) (user User, err error) { 27 | v = cleanValues(v) 28 | v.Set("user_id", strconv.FormatInt(id, 10)) 29 | return a.Mute(v) 30 | } 31 | 32 | func (a TwitterApi) Mute(v url.Values) (user User, err error) { 33 | response_ch := make(chan response) 34 | a.queryQueue <- query{a.baseUrl + "/mutes/users/create.json", v, &user, _POST, response_ch} 35 | return user, (<-response_ch).err 36 | } 37 | 38 | func (a TwitterApi) UnmuteUser(screenName string, v url.Values) (user User, err error) { 39 | v = cleanValues(v) 40 | v.Set("screen_name", screenName) 41 | return a.Unmute(v) 42 | } 43 | 44 | func (a TwitterApi) UnmuteUserId(id int64, v url.Values) (user User, err error) { 45 | v = cleanValues(v) 46 | v.Set("user_id", strconv.FormatInt(id, 10)) 47 | return a.Unmute(v) 48 | } 49 | 50 | func (a TwitterApi) Unmute(v url.Values) (user User, err error) { 51 | response_ch := make(chan response) 52 | a.queryQueue <- query{a.baseUrl + "/mutes/users/destroy.json", v, &user, _POST, response_ch} 53 | return user, (<-response_ch).err 54 | } 55 | -------------------------------------------------------------------------------- /oembed.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "strconv" 7 | ) 8 | 9 | type OEmbed struct { 10 | Type string 11 | Width int 12 | Cache_age string 13 | Height int 14 | Author_url string 15 | Html string 16 | Version string 17 | Provider_name string 18 | Provider_url string 19 | Url string 20 | Author_name string 21 | } 22 | 23 | // No authorization on this endpoint. Its the only one. 24 | func (a TwitterApi) GetOEmbed(v url.Values) (o OEmbed, err error) { 25 | resp, err := http.Get(a.baseUrlV1() + "/statuses/oembed.json?" + v.Encode()) 26 | if err != nil { 27 | return 28 | } 29 | defer resp.Body.Close() 30 | 31 | err = decodeResponse(resp, &o) 32 | return 33 | } 34 | 35 | // Calls GetOEmbed with the corresponding id. Convenience wrapper for GetOEmbed() 36 | func (a TwitterApi) GetOEmbedId(id int64, v url.Values) (o OEmbed, err error) { 37 | v = cleanValues(v) 38 | v.Set("id", strconv.FormatInt(id, 10)) 39 | resp, err := http.Get(a.baseUrlV1() + "/statuses/oembed.json?" + v.Encode()) 40 | if err != nil { 41 | return 42 | } 43 | defer resp.Body.Close() 44 | 45 | err = decodeResponse(resp, &o) 46 | return 47 | } 48 | 49 | func (a TwitterApi) baseUrlV1() string { 50 | if a.baseUrl == BaseUrl { 51 | return BaseUrlV1 52 | } 53 | 54 | if a.baseUrl == "" { 55 | return BaseUrlV1 56 | } 57 | 58 | return a.baseUrl 59 | } 60 | -------------------------------------------------------------------------------- /oembed_test.go: -------------------------------------------------------------------------------- 1 | package anaconda_test 2 | 3 | import ( 4 | "net/url" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/ChimeraCoder/anaconda" 9 | ) 10 | 11 | func TestOEmbed(t *testing.T) { 12 | // It is the only one that can be tested without auth 13 | // However, it is still rate-limited 14 | api := anaconda.NewTwitterApi("", "") 15 | api.SetBaseUrl(testBase) 16 | o, err := api.GetOEmbed(url.Values{"id": []string{"99530515043983360"}}) 17 | if err != nil { 18 | t.Error(err) 19 | } 20 | 21 | if !reflect.DeepEqual(o, expectedOEmbed) { 22 | t.Errorf("Actual OEmbed differs expected:\n%#v\n Got: \n%#v\n", expectedOEmbed, o) 23 | } 24 | } 25 | 26 | var expectedOEmbed anaconda.OEmbed = anaconda.OEmbed{ 27 | Cache_age: "3153600000", 28 | Url: "https://twitter.com/twitter/status/99530515043983360", 29 | Height: 0, 30 | Provider_url: "https://twitter.com", 31 | Provider_name: "Twitter", 32 | Author_name: "Twitter", 33 | Version: "1.0", 34 | Author_url: "https://twitter.com/twitter", 35 | Type: "rich", 36 | Html: `

Cool! “@tw1tt3rart: #TWITTERART ╱╱╱╱╱╱╱╱ ╱╱╭━━━━╮╱╱╭━━━━╮ ╱╱┃▇┆┆▇┃╱╭┫ⓦⓔⓔⓚ┃ ╱╱┃▽▽▽▽┃━╯┃♡ⓔⓝⓓ┃ ╱╭┫△△△△┣╮╱╰━━━━╯ ╱┃┃┆┆┆┆┃┃╱╱╱╱╱╱ ╱┗┫┆┏┓┆┣┛╱╱╱╱╱”

— Twitter (@twitter) August 5, 2011
37 | `, 38 | Width: 550, 39 | } 40 | -------------------------------------------------------------------------------- /place.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | type Place struct { 4 | Attributes map[string]string `json:"attributes"` 5 | BoundingBox struct { 6 | Coordinates [][][]float64 `json:"coordinates"` 7 | Type string `json:"type"` 8 | } `json:"bounding_box"` 9 | ContainedWithin []struct { 10 | Attributes map[string]string `json:"attributes"` 11 | BoundingBox struct { 12 | Coordinates [][][]float64 `json:"coordinates"` 13 | Type string `json:"type"` 14 | } `json:"bounding_box"` 15 | Country string `json:"country"` 16 | CountryCode string `json:"country_code"` 17 | FullName string `json:"full_name"` 18 | ID string `json:"id"` 19 | Name string `json:"name"` 20 | PlaceType string `json:"place_type"` 21 | URL string `json:"url"` 22 | } `json:"contained_within"` 23 | Country string `json:"country"` 24 | CountryCode string `json:"country_code"` 25 | FullName string `json:"full_name"` 26 | Geometry struct { 27 | Coordinates [][][]float64 `json:"coordinates"` 28 | Type string `json:"type"` 29 | } `json:"geometry"` 30 | ID string `json:"id"` 31 | Name string `json:"name"` 32 | PlaceType string `json:"place_type"` 33 | Polylines []string `json:"polylines"` 34 | URL string `json:"url"` 35 | } 36 | -------------------------------------------------------------------------------- /rate_limit_status.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | "strings" 6 | ) 7 | 8 | type RateLimitStatusResponse struct { 9 | RateLimitContext RateLimitContext `json:"rate_limit_context"` 10 | Resources map[string]map[string]BaseResource `json:"resources"` 11 | } 12 | 13 | type RateLimitContext struct { 14 | AccessToken string `json:"access_token"` 15 | } 16 | 17 | type BaseResource struct { 18 | Limit int `json:"limit"` 19 | Remaining int `json:"remaining"` 20 | Reset int `json:"reset"` 21 | } 22 | 23 | func (a TwitterApi) GetRateLimits(r []string) (rateLimitStatusResponse RateLimitStatusResponse, err error) { 24 | resources := strings.Join(r, ",") 25 | v := url.Values{} 26 | v.Set("resources", resources) 27 | response_ch := make(chan response) 28 | a.queryQueue <- query{a.baseUrl + "/application/rate_limit_status.json", v, &rateLimitStatusResponse, _GET, response_ch} 29 | return rateLimitStatusResponse, (<-response_ch).err 30 | } 31 | -------------------------------------------------------------------------------- /relationship.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | type RelationshipResponse struct { 8 | Relationship Relationship `json:"relationship"` 9 | } 10 | type Relationship struct { 11 | Target Target `json:"target"` 12 | Source Source `json:"source"` 13 | } 14 | type Target struct { 15 | Id int64 `json:"id"` 16 | Id_str string `json:"id_str"` 17 | Screen_name string `json:"screen_name"` 18 | Following bool `json:"following"` 19 | Followed_by bool `json:"followed_by"` 20 | } 21 | type Source struct { 22 | Id int64 23 | Id_str string 24 | Screen_name string 25 | Following bool 26 | Followed_by bool 27 | Can_dm bool 28 | Blocking bool 29 | Muting bool 30 | Marked_spam bool 31 | All_replies bool 32 | Want_retweets bool 33 | Notifications_enabled bool 34 | } 35 | 36 | func (a TwitterApi) GetFriendshipsShow(v url.Values) (relationshipResponse RelationshipResponse, err error) { 37 | response_ch := make(chan response) 38 | a.queryQueue <- query{a.baseUrl + "/friendships/show.json", v, &relationshipResponse, _GET, response_ch} 39 | return relationshipResponse, (<-response_ch).err 40 | } 41 | -------------------------------------------------------------------------------- /search.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | type SearchMetadata struct { 8 | CompletedIn float32 `json:"completed_in"` 9 | MaxId int64 `json:"max_id"` 10 | MaxIdString string `json:"max_id_str"` 11 | Query string `json:"query"` 12 | RefreshUrl string `json:"refresh_url"` 13 | Count int `json:"count"` 14 | SinceId int64 `json:"since_id"` 15 | SinceIdString string `json:"since_id_str"` 16 | NextResults string `json:"next_results"` 17 | } 18 | 19 | type SearchResponse struct { 20 | Statuses []Tweet `json:"statuses"` 21 | Metadata SearchMetadata `json:"search_metadata"` 22 | } 23 | 24 | func (sr *SearchResponse) GetNext(a *TwitterApi) (SearchResponse, error) { 25 | if sr.Metadata.NextResults == "" { 26 | return SearchResponse{}, nil 27 | } 28 | nextUrl, err := url.Parse(sr.Metadata.NextResults) 29 | if err != nil { 30 | return SearchResponse{}, err 31 | } 32 | 33 | v := nextUrl.Query() 34 | // remove the q parameter from the url.Values so that it 35 | // can be added back via the next GetSearch method call. 36 | delete(v, "q") 37 | 38 | q, _ := url.QueryUnescape(sr.Metadata.Query) 39 | if err != nil { 40 | return SearchResponse{}, err 41 | } 42 | newSr, err := a.GetSearch(q, v) 43 | return newSr, err 44 | } 45 | 46 | func (a TwitterApi) GetSearch(queryString string, v url.Values) (sr SearchResponse, err error) { 47 | v = cleanValues(v) 48 | v.Set("q", queryString) 49 | response_ch := make(chan response) 50 | a.queryQueue <- query{a.baseUrl + "/search/tweets.json", v, &sr, _GET, response_ch} 51 | 52 | // We have to read from the response channel before assigning to timeline 53 | // Otherwise this will happen before the responses have been written 54 | resp := <-response_ch 55 | err = resp.err 56 | return sr, err 57 | } 58 | -------------------------------------------------------------------------------- /timeline.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | // GetHomeTimeline returns the most recent tweets and retweets posted by the user 8 | // and the users that they follow. 9 | // https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline 10 | // By default, include_entities is set to "true" 11 | func (a TwitterApi) GetHomeTimeline(v url.Values) (timeline []Tweet, err error) { 12 | v = cleanValues(v) 13 | if val := v.Get("include_entities"); val == "" { 14 | v.Set("include_entities", "true") 15 | } 16 | 17 | response_ch := make(chan response) 18 | a.queryQueue <- query{a.baseUrl + "/statuses/home_timeline.json", v, &timeline, _GET, response_ch} 19 | return timeline, (<-response_ch).err 20 | } 21 | 22 | // GetUserTimeline returns a collection of the most recent Tweets posted by the user indicated by the screen_name or user_id parameters. 23 | // https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-user_timeline 24 | func (a TwitterApi) GetUserTimeline(v url.Values) (timeline []Tweet, err error) { 25 | response_ch := make(chan response) 26 | a.queryQueue <- query{a.baseUrl + "/statuses/user_timeline.json", v, &timeline, _GET, response_ch} 27 | return timeline, (<-response_ch).err 28 | } 29 | 30 | // GetMentionsTimeline returns the most recent mentions (Tweets containing a users’s @screen_name) for the authenticating user. 31 | // The timeline returned is the equivalent of the one seen when you view your mentions on twitter.com. 32 | // https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-mentions_timeline 33 | func (a TwitterApi) GetMentionsTimeline(v url.Values) (timeline []Tweet, err error) { 34 | response_ch := make(chan response) 35 | a.queryQueue <- query{a.baseUrl + "/statuses/mentions_timeline.json", v, &timeline, _GET, response_ch} 36 | return timeline, (<-response_ch).err 37 | } 38 | 39 | // GetRetweetsOfMe returns the most recent Tweets authored by the authenticating user that have been retweeted by others. 40 | // https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-retweets_of_me 41 | func (a TwitterApi) GetRetweetsOfMe(v url.Values) (tweets []Tweet, err error) { 42 | response_ch := make(chan response) 43 | a.queryQueue <- query{a.baseUrl + "/statuses/retweets_of_me.json", v, &tweets, _GET, response_ch} 44 | return tweets, (<-response_ch).err 45 | } 46 | -------------------------------------------------------------------------------- /trends.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | ) 7 | 8 | type Location struct { 9 | Name string `json:"name"` 10 | Woeid int `json:"woeid"` 11 | } 12 | 13 | type Trend struct { 14 | Name string `json:"name"` 15 | Query string `json:"query"` 16 | Url string `json:"url"` 17 | PromotedContent string `json:"promoted_content"` 18 | } 19 | 20 | type TrendResponse struct { 21 | Trends []Trend `json:"trends"` 22 | AsOf string `json:"as_of"` 23 | CreatedAt string `json:"created_at"` 24 | Locations []Location `json:"locations"` 25 | } 26 | 27 | type TrendLocation struct { 28 | Country string `json:"country"` 29 | CountryCode string `json:"countryCode"` 30 | Name string `json:"name"` 31 | ParentId int `json:"parentid"` 32 | PlaceType struct { 33 | Code int `json:"code"` 34 | Name string `json:"name"` 35 | } `json:"placeType"` 36 | Url string `json:"url"` 37 | Woeid int32 `json:"woeid"` 38 | } 39 | 40 | // https://developer.twitter.com/en/docs/trends/trends-for-location/api-reference/get-trends-place 41 | func (a TwitterApi) GetTrendsByPlace(id int64, v url.Values) (trendResp TrendResponse, err error) { 42 | response_ch := make(chan response) 43 | v = cleanValues(v) 44 | v.Set("id", strconv.FormatInt(id, 10)) 45 | a.queryQueue <- query{a.baseUrl + "/trends/place.json", v, &[]interface{}{&trendResp}, _GET, response_ch} 46 | return trendResp, (<-response_ch).err 47 | } 48 | 49 | // https://developer.twitter.com/en/docs/trends/locations-with-trending-topics/api-reference/get-trends-available 50 | func (a TwitterApi) GetTrendsAvailableLocations(v url.Values) (locations []TrendLocation, err error) { 51 | response_ch := make(chan response) 52 | a.queryQueue <- query{a.baseUrl + "/trends/available.json", v, &locations, _GET, response_ch} 53 | return locations, (<-response_ch).err 54 | } 55 | 56 | // https://developer.twitter.com/en/docs/trends/locations-with-trending-topics/api-reference/get-trends-closest 57 | func (a TwitterApi) GetTrendsClosestLocations(lat float64, long float64, v url.Values) (locations []TrendLocation, err error) { 58 | response_ch := make(chan response) 59 | v = cleanValues(v) 60 | v.Set("lat", strconv.FormatFloat(lat, 'f', 6, 64)) 61 | v.Set("long", strconv.FormatFloat(long, 'f', 6, 64)) 62 | a.queryQueue <- query{a.baseUrl + "/trends/closest.json", v, &locations, _GET, response_ch} 63 | return locations, (<-response_ch).err 64 | } 65 | -------------------------------------------------------------------------------- /tweet.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | type Tweet struct { 10 | Contributors []int64 `json:"contributors"` 11 | Coordinates *Coordinates `json:"coordinates"` 12 | CreatedAt string `json:"created_at"` 13 | DisplayTextRange []int `json:"display_text_range"` 14 | Entities Entities `json:"entities"` 15 | ExtendedEntities Entities `json:"extended_entities"` 16 | ExtendedTweet ExtendedTweet `json:"extended_tweet"` 17 | FavoriteCount int `json:"favorite_count"` 18 | Favorited bool `json:"favorited"` 19 | FilterLevel string `json:"filter_level"` 20 | FullText string `json:"full_text"` 21 | HasExtendedProfile bool `json:"has_extended_profile"` 22 | Id int64 `json:"id"` 23 | IdStr string `json:"id_str"` 24 | InReplyToScreenName string `json:"in_reply_to_screen_name"` 25 | InReplyToStatusID int64 `json:"in_reply_to_status_id"` 26 | InReplyToStatusIdStr string `json:"in_reply_to_status_id_str"` 27 | InReplyToUserID int64 `json:"in_reply_to_user_id"` 28 | InReplyToUserIdStr string `json:"in_reply_to_user_id_str"` 29 | IsTranslationEnabled bool `json:"is_translation_enabled"` 30 | Lang string `json:"lang"` 31 | Place Place `json:"place"` 32 | QuotedStatusID int64 `json:"quoted_status_id"` 33 | QuotedStatusIdStr string `json:"quoted_status_id_str"` 34 | QuotedStatus *Tweet `json:"quoted_status"` 35 | PossiblySensitive bool `json:"possibly_sensitive"` 36 | PossiblySensitiveAppealable bool `json:"possibly_sensitive_appealable"` 37 | RetweetCount int `json:"retweet_count"` 38 | Retweeted bool `json:"retweeted"` 39 | RetweetedStatus *Tweet `json:"retweeted_status"` 40 | Source string `json:"source"` 41 | Scopes map[string]interface{} `json:"scopes"` 42 | Text string `json:"text"` 43 | User User `json:"user"` 44 | WithheldCopyright bool `json:"withheld_copyright"` 45 | WithheldInCountries []string `json:"withheld_in_countries"` 46 | WithheldScope string `json:"withheld_scope"` 47 | 48 | //Geo is deprecated 49 | //Geo interface{} `json:"geo"` 50 | } 51 | 52 | // CreatedAtTime is a convenience wrapper that returns the Created_at time, parsed as a time.Time struct 53 | func (t Tweet) CreatedAtTime() (time.Time, error) { 54 | return time.Parse(time.RubyDate, t.CreatedAt) 55 | } 56 | 57 | // It may be worth placing these in an additional source file(s) 58 | 59 | // Could also use User, since the fields match, but only these fields are possible in Contributor 60 | type Contributor struct { 61 | Id int64 `json:"id"` 62 | IdStr string `json:"id_str"` 63 | ScreenName string `json:"screen_name"` 64 | } 65 | 66 | type Coordinates struct { 67 | Coordinates [2]float64 `json:"coordinates"` // Coordinate always has to have exactly 2 values 68 | Type string `json:"type"` 69 | } 70 | 71 | type ExtendedTweet struct { 72 | FullText string `json:"full_text"` 73 | DisplayTextRange []int `json:"display_text_range"` 74 | Entities Entities `json:"entities"` 75 | ExtendedEntities Entities `json:"extended_entities"` 76 | } 77 | 78 | // HasCoordinates is a helper function to easily determine if a Tweet has coordinates associated with it 79 | func (t Tweet) HasCoordinates() bool { 80 | if t.Coordinates != nil { 81 | if t.Coordinates.Type == "Point" { 82 | return true 83 | } 84 | } 85 | return false 86 | } 87 | 88 | // The following provide convenience and eliviate confusion about the order of coordinates in the Tweet 89 | 90 | // Latitude is a convenience wrapper that returns the latitude easily 91 | func (t Tweet) Latitude() (float64, error) { 92 | if t.HasCoordinates() { 93 | return t.Coordinates.Coordinates[1], nil 94 | } 95 | return 0, fmt.Errorf("No Coordinates in this Tweet") 96 | } 97 | 98 | // Longitude is a convenience wrapper that returns the longitude easily 99 | func (t Tweet) Longitude() (float64, error) { 100 | if t.HasCoordinates() { 101 | return t.Coordinates.Coordinates[0], nil 102 | } 103 | return 0, fmt.Errorf("No Coordinates in this Tweet") 104 | } 105 | 106 | // X is a convenience wrapper which returns the X (Longitude) coordinate easily 107 | func (t Tweet) X() (float64, error) { 108 | return t.Longitude() 109 | } 110 | 111 | // Y is a convenience wrapper which return the Y (Lattitude) corrdinate easily 112 | func (t Tweet) Y() (float64, error) { 113 | return t.Latitude() 114 | } 115 | 116 | func (t *Tweet) extractExtendedTweet() { 117 | // if the TruncatedText is set, the API does not return an extended tweet 118 | // we need to manually set the Text in this case 119 | if len(t.Text) > 0 && len(t.FullText) == 0 { 120 | t.FullText = t.Text 121 | } 122 | 123 | if len(t.ExtendedTweet.FullText) > 0 { 124 | t.DisplayTextRange = t.ExtendedTweet.DisplayTextRange 125 | t.Entities = t.ExtendedTweet.Entities 126 | t.ExtendedEntities = t.ExtendedTweet.ExtendedEntities 127 | t.FullText = t.ExtendedTweet.FullText 128 | } 129 | 130 | // if the API supplied us with information how to extract the shortened 131 | // text, extract it 132 | if len(t.Text) == 0 && len(t.DisplayTextRange) == 2 { 133 | t.Text = t.FullText[t.DisplayTextRange[0]:t.DisplayTextRange[1]] 134 | } 135 | // if the truncated text is still empty then full & truncated text are equal 136 | if len(t.Text) == 0 { 137 | t.Text = t.FullText 138 | } 139 | } 140 | 141 | func (t *Tweet) UnmarshalJSON(data []byte) error { 142 | type Alias Tweet 143 | aux := &struct { 144 | *Alias 145 | }{ 146 | Alias: (*Alias)(t), 147 | } 148 | if err := json.Unmarshal(data, &aux); err != nil { 149 | return err 150 | } 151 | 152 | t.extractExtendedTweet() 153 | return nil 154 | } 155 | -------------------------------------------------------------------------------- /tweets.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | ) 8 | 9 | func (a TwitterApi) GetTweet(id int64, v url.Values) (tweet Tweet, err error) { 10 | v = cleanValues(v) 11 | v.Set("id", strconv.FormatInt(id, 10)) 12 | 13 | response_ch := make(chan response) 14 | a.queryQueue <- query{a.baseUrl + "/statuses/show.json", v, &tweet, _GET, response_ch} 15 | return tweet, (<-response_ch).err 16 | } 17 | 18 | func (a TwitterApi) GetTweetsLookupByIds(ids []int64, v url.Values) (tweet []Tweet, err error) { 19 | var pids string 20 | for w, i := range ids { 21 | pids += strconv.FormatInt(i, 10) 22 | if w != len(ids)-1 { 23 | pids += "," 24 | } 25 | } 26 | v = cleanValues(v) 27 | v.Set("id", pids) 28 | response_ch := make(chan response) 29 | a.queryQueue <- query{a.baseUrl + "/statuses/lookup.json", v, &tweet, _GET, response_ch} 30 | return tweet, (<-response_ch).err 31 | } 32 | 33 | func (a TwitterApi) GetRetweets(id int64, v url.Values) (tweets []Tweet, err error) { 34 | response_ch := make(chan response) 35 | a.queryQueue <- query{a.baseUrl + fmt.Sprintf("/statuses/retweets/%d.json", id), v, &tweets, _GET, response_ch} 36 | return tweets, (<-response_ch).err 37 | } 38 | 39 | //PostTweet will create a tweet with the specified status message 40 | func (a TwitterApi) PostTweet(status string, v url.Values) (tweet Tweet, err error) { 41 | v = cleanValues(v) 42 | v.Set("status", status) 43 | response_ch := make(chan response) 44 | a.queryQueue <- query{a.baseUrl + "/statuses/update.json", v, &tweet, _POST, response_ch} 45 | return tweet, (<-response_ch).err 46 | } 47 | 48 | //DeleteTweet will destroy (delete) the status (tweet) with the specified ID, assuming that the authenticated user is the author of the status (tweet). 49 | //If trimUser is set to true, only the user's Id will be provided in the user object returned. 50 | func (a TwitterApi) DeleteTweet(id int64, trimUser bool) (tweet Tweet, err error) { 51 | v := url.Values{} 52 | if trimUser { 53 | v.Set("trim_user", "t") 54 | } 55 | response_ch := make(chan response) 56 | a.queryQueue <- query{a.baseUrl + fmt.Sprintf("/statuses/destroy/%d.json", id), v, &tweet, _POST, response_ch} 57 | return tweet, (<-response_ch).err 58 | } 59 | 60 | //Retweet will retweet the status (tweet) with the specified ID. 61 | //trimUser functions as in DeleteTweet 62 | func (a TwitterApi) Retweet(id int64, trimUser bool) (rt Tweet, err error) { 63 | v := url.Values{} 64 | if trimUser { 65 | v.Set("trim_user", "t") 66 | } 67 | response_ch := make(chan response) 68 | a.queryQueue <- query{a.baseUrl + fmt.Sprintf("/statuses/retweet/%d.json", id), v, &rt, _POST, response_ch} 69 | return rt, (<-response_ch).err 70 | } 71 | 72 | //UnRetweet will renove retweet Untweets a retweeted status. 73 | //Returns the original Tweet with retweet details embedded. 74 | // 75 | //https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-unretweet-id 76 | //trim_user: tweet returned in a timeline will include a user object 77 | //including only the status authors numerical ID. 78 | func (a TwitterApi) UnRetweet(id int64, trimUser bool) (rt Tweet, err error) { 79 | v := url.Values{} 80 | if trimUser { 81 | v.Set("trim_user", "t") 82 | } 83 | response_ch := make(chan response) 84 | a.queryQueue <- query{a.baseUrl + fmt.Sprintf("/statuses/unretweet/%d.json", id), v, &rt, _POST, response_ch} 85 | return rt, (<-response_ch).err 86 | } 87 | 88 | // Favorite will favorite the status (tweet) with the specified ID. 89 | // https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-favorites-create 90 | func (a TwitterApi) Favorite(id int64) (rt Tweet, err error) { 91 | v := url.Values{} 92 | v.Set("id", fmt.Sprint(id)) 93 | response_ch := make(chan response) 94 | a.queryQueue <- query{a.baseUrl + fmt.Sprintf("/favorites/create.json"), v, &rt, _POST, response_ch} 95 | return rt, (<-response_ch).err 96 | } 97 | 98 | // Un-favorites the status specified in the ID parameter as the authenticating user. 99 | // Returns the un-favorited status in the requested format when successful. 100 | // https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-favorites-destroy 101 | func (a TwitterApi) Unfavorite(id int64) (rt Tweet, err error) { 102 | v := url.Values{} 103 | v.Set("id", fmt.Sprint(id)) 104 | response_ch := make(chan response) 105 | a.queryQueue <- query{a.baseUrl + fmt.Sprintf("/favorites/destroy.json"), v, &rt, _POST, response_ch} 106 | return rt, (<-response_ch).err 107 | } 108 | -------------------------------------------------------------------------------- /twitter_entities.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | type UrlEntity struct { 4 | Urls []struct { 5 | Indices []int `json:"indices"` 6 | Url string `json:"url"` 7 | Display_url string `json:"display_url"` 8 | Expanded_url string `json:"expanded_url"` 9 | } `json:"urls"` 10 | } 11 | 12 | type Entities struct { 13 | Urls []struct { 14 | Indices []int `json:"indices"` 15 | Url string `json:"url"` 16 | Display_url string `json:"display_url"` 17 | Expanded_url string `json:"expanded_url"` 18 | } `json:"urls"` 19 | Hashtags []struct { 20 | Indices []int `json:"indices"` 21 | Text string `json:"text"` 22 | } `json:"hashtags"` 23 | Url UrlEntity `json:"url"` 24 | User_mentions []struct { 25 | Name string `json:"name"` 26 | Indices []int `json:"indices"` 27 | Screen_name string `json:"screen_name"` 28 | Id int64 `json:"id"` 29 | Id_str string `json:"id_str"` 30 | } `json:"user_mentions"` 31 | Media []EntityMedia `json:"media"` 32 | } 33 | 34 | type EntityMedia struct { 35 | Id int64 `json:"id"` 36 | Id_str string `json:"id_str"` 37 | Media_url string `json:"media_url"` 38 | Media_url_https string `json:"media_url_https"` 39 | Url string `json:"url"` 40 | Display_url string `json:"display_url"` 41 | Expanded_url string `json:"expanded_url"` 42 | Sizes MediaSizes `json:"sizes"` 43 | Source_status_id int64 `json:"source_status_id"` 44 | Source_status_id_str string `json:"source_status_id_str"` 45 | Type string `json:"type"` 46 | Indices []int `json:"indices"` 47 | VideoInfo VideoInfo `json:"video_info"` 48 | ExtAltText string `json:"ext_alt_text"` 49 | } 50 | 51 | type MediaSizes struct { 52 | Medium MediaSize `json:"medium"` 53 | Thumb MediaSize `json:"thumb"` 54 | Small MediaSize `json:"small"` 55 | Large MediaSize `json:"large"` 56 | } 57 | 58 | type MediaSize struct { 59 | W int `json:"w"` 60 | H int `json:"h"` 61 | Resize string `json:"resize"` 62 | } 63 | 64 | type VideoInfo struct { 65 | AspectRatio []int `json:"aspect_ratio"` 66 | DurationMillis int64 `json:"duration_millis"` 67 | Variants []Variant `json:"variants"` 68 | } 69 | 70 | type Variant struct { 71 | Bitrate int `json:"bitrate"` 72 | ContentType string `json:"content_type"` 73 | Url string `json:"url"` 74 | } 75 | 76 | type Category struct { 77 | Name string `json:"name"` 78 | Slug string `json:"slug"` 79 | Size int `json:"size"` 80 | } 81 | 82 | type Suggestions struct { 83 | Category 84 | Users []User 85 | } 86 | -------------------------------------------------------------------------------- /twitter_user.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | type User struct { 4 | ContributorsEnabled bool `json:"contributors_enabled"` 5 | CreatedAt string `json:"created_at"` 6 | DefaultProfile bool `json:"default_profile"` 7 | DefaultProfileImage bool `json:"default_profile_image"` 8 | Description string `json:"description"` 9 | Email string `json:"email"` 10 | Entities Entities `json:"entities"` 11 | FavouritesCount int `json:"favourites_count"` 12 | FollowRequestSent bool `json:"follow_request_sent"` 13 | FollowersCount int `json:"followers_count"` 14 | Following bool `json:"following"` 15 | FriendsCount int `json:"friends_count"` 16 | GeoEnabled bool `json:"geo_enabled"` 17 | HasExtendedProfile bool `json:"has_extended_profile"` 18 | Id int64 `json:"id"` 19 | IdStr string `json:"id_str"` 20 | IsTranslator bool `json:"is_translator"` 21 | IsTranslationEnabled bool `json:"is_translation_enabled"` 22 | Lang string `json:"lang"` // BCP-47 code of user defined language 23 | ListedCount int64 `json:"listed_count"` 24 | Location string `json:"location"` // User defined location 25 | Name string `json:"name"` 26 | Notifications bool `json:"notifications"` 27 | ProfileBackgroundColor string `json:"profile_background_color"` 28 | ProfileBackgroundImageURL string `json:"profile_background_image_url"` 29 | ProfileBackgroundImageUrlHttps string `json:"profile_background_image_url_https"` 30 | ProfileBackgroundTile bool `json:"profile_background_tile"` 31 | ProfileBannerURL string `json:"profile_banner_url"` 32 | ProfileImageURL string `json:"profile_image_url"` 33 | ProfileImageUrlHttps string `json:"profile_image_url_https"` 34 | ProfileLinkColor string `json:"profile_link_color"` 35 | ProfileSidebarBorderColor string `json:"profile_sidebar_border_color"` 36 | ProfileSidebarFillColor string `json:"profile_sidebar_fill_color"` 37 | ProfileTextColor string `json:"profile_text_color"` 38 | ProfileUseBackgroundImage bool `json:"profile_use_background_image"` 39 | Protected bool `json:"protected"` 40 | ScreenName string `json:"screen_name"` 41 | ShowAllInlineMedia bool `json:"show_all_inline_media"` 42 | Status *Tweet `json:"status"` // Only included if the user is a friend 43 | StatusesCount int64 `json:"statuses_count"` 44 | TimeZone string `json:"time_zone"` 45 | URL string `json:"url"` 46 | UtcOffset int `json:"utc_offset"` 47 | Verified bool `json:"verified"` 48 | WithheldInCountries []string `json:"withheld_in_countries"` 49 | WithheldScope string `json:"withheld_scope"` 50 | } 51 | 52 | // Provide language translator from BCP-47 to human readable format for Lang field? 53 | // Available through golang.org/x/text/language, deserves further investigation 54 | -------------------------------------------------------------------------------- /users.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | ) 7 | 8 | func (a TwitterApi) GetUsersLookup(usernames string, v url.Values) (u []User, err error) { 9 | v = cleanValues(v) 10 | v.Set("screen_name", usernames) 11 | response_ch := make(chan response) 12 | a.queryQueue <- query{a.baseUrl + "/users/lookup.json", v, &u, _GET, response_ch} 13 | return u, (<-response_ch).err 14 | } 15 | 16 | func (a TwitterApi) GetUsersLookupByIds(ids []int64, v url.Values) (u []User, err error) { 17 | var pids string 18 | for w, i := range ids { 19 | //pids += strconv.Itoa(i) 20 | pids += strconv.FormatInt(i, 10) 21 | if w != len(ids)-1 { 22 | pids += "," 23 | } 24 | } 25 | v = cleanValues(v) 26 | v.Set("user_id", pids) 27 | response_ch := make(chan response) 28 | a.queryQueue <- query{a.baseUrl + "/users/lookup.json", v, &u, _GET, response_ch} 29 | return u, (<-response_ch).err 30 | } 31 | 32 | func (a TwitterApi) GetUsersShow(username string, v url.Values) (u User, err error) { 33 | v = cleanValues(v) 34 | v.Set("screen_name", username) 35 | response_ch := make(chan response) 36 | a.queryQueue <- query{a.baseUrl + "/users/show.json", v, &u, _GET, response_ch} 37 | return u, (<-response_ch).err 38 | } 39 | 40 | func (a TwitterApi) GetUsersShowById(id int64, v url.Values) (u User, err error) { 41 | v = cleanValues(v) 42 | v.Set("user_id", strconv.FormatInt(id, 10)) 43 | response_ch := make(chan response) 44 | a.queryQueue <- query{a.baseUrl + "/users/show.json", v, &u, _GET, response_ch} 45 | return u, (<-response_ch).err 46 | } 47 | 48 | func (a TwitterApi) GetUserSearch(searchTerm string, v url.Values) (u []User, err error) { 49 | v = cleanValues(v) 50 | v.Set("q", searchTerm) 51 | // Set other values before calling this method: 52 | // page, count, include_entities 53 | response_ch := make(chan response) 54 | a.queryQueue <- query{a.baseUrl + "/users/search.json", v, &u, _GET, response_ch} 55 | return u, (<-response_ch).err 56 | } 57 | 58 | func (a TwitterApi) GetUsersSuggestions(v url.Values) (c []Category, err error) { 59 | v = cleanValues(v) 60 | response_ch := make(chan response) 61 | a.queryQueue <- query{a.baseUrl + "/users/suggestions.json", v, &c, _GET, response_ch} 62 | return c, (<-response_ch).err 63 | } 64 | 65 | func (a TwitterApi) GetUsersSuggestionsBySlug(slug string, v url.Values) (s Suggestions, err error) { 66 | v = cleanValues(v) 67 | v.Set("slug", slug) 68 | response_ch := make(chan response) 69 | a.queryQueue <- query{a.baseUrl + "/users/suggestions/" + slug + ".json", v, &s, _GET, response_ch} 70 | return s, (<-response_ch).err 71 | } 72 | 73 | // PostUsersReportSpam : Reports and Blocks a User by screen_name 74 | // Reference : https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-users-report_spam 75 | // If you don't want to block the user you should add 76 | // v.Set("perform_block", "false") 77 | func (a TwitterApi) PostUsersReportSpam(username string, v url.Values) (u User, err error) { 78 | v = cleanValues(v) 79 | v.Set("screen_name", username) 80 | response_ch := make(chan response) 81 | a.queryQueue <- query{a.baseUrl + "/users/report_spam.json", v, &u, _POST, response_ch} 82 | return u, (<-response_ch).err 83 | } 84 | 85 | // PostUsersReportSpamById : Reports and Blocks a User by user_id 86 | // Reference : https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-users-report_spam 87 | // If you don't want to block the user you should add 88 | // v.Set("perform_block", "false") 89 | func (a TwitterApi) PostUsersReportSpamById(id int64, v url.Values) (u User, err error) { 90 | v = cleanValues(v) 91 | v.Set("user_id", strconv.FormatInt(id, 10)) 92 | response_ch := make(chan response) 93 | a.queryQueue <- query{a.baseUrl + "/users/report_spam.json", v, &u, _POST, response_ch} 94 | return u, (<-response_ch).err 95 | } 96 | 97 | // PostAccountUpdateProfile updates the active users profile with the provided values 98 | func (a TwitterApi) PostAccountUpdateProfile(v url.Values) (u User, err error) { 99 | v = cleanValues(v) 100 | response_ch := make(chan response) 101 | a.queryQueue <- query{a.baseUrl + "/account/update_profile.json", v, &u, _POST, response_ch} 102 | return u, (<-response_ch).err 103 | } 104 | -------------------------------------------------------------------------------- /vendor/github.com/ChimeraCoder/tokenbucket/.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.swn 4 | conf.sh 5 | -------------------------------------------------------------------------------- /vendor/github.com/ChimeraCoder/tokenbucket/LICENSE: -------------------------------------------------------------------------------- 1 | COPYING -------------------------------------------------------------------------------- /vendor/github.com/ChimeraCoder/tokenbucket/README: -------------------------------------------------------------------------------- 1 | [![GoDoc](http://godoc.org/github.com/ChimeraCoder/tokenbucket?status.png)](http://godoc.org/github.com/ChimeraCoder/tokenbucket) 2 | 3 | tokenbucket 4 | ==================== 5 | 6 | This package provides an implementation of [Token bucket](https://en.wikipedia.org/wiki/Token_bucket) scheduling in Go. It is useful for implementing rate-limiting, traffic shaping, or other sorts of scheduling that depend on bandwidth constraints. 7 | 8 | 9 | Example 10 | ------------ 11 | 12 | 13 | To create a new bucket, specify a capacity (how many tokens can be stored "in the bank"), and a rate (how often a new token is added). 14 | 15 | ````go 16 | 17 | // Create a new bucket 18 | // Allow a new action every 5 seconds, with a maximum of 3 "in the bank" 19 | bucket := tokenbucket.NewBucket(3, 5 * time.Second) 20 | ```` 21 | 22 | This bucket should be shared between any functions that share the same constraints. (These functions may or may not run in separate goroutines). 23 | 24 | 25 | Anytime a regulated action is performed, spend a token. 26 | 27 | ````go 28 | // To perform a regulated action, we must spend a token 29 | // RegulatedAction will not be performed until the bucket contains enough tokens 30 | <-bucket.SpendToken(1) 31 | RegulatedAction() 32 | ```` 33 | 34 | `SpendToken` returns immediately. Reading from the channel that it returns will block until the action has "permission" to continue (ie, until there are enough tokens in the bucket). 35 | 36 | 37 | (The channel that `SpendToken` returns is of type `error`. For now, the value will always be `nil`, so it can be ignored.) 38 | 39 | 40 | 41 | ####License 42 | 43 | `tokenbucket` is free software provided under version 3 of the LGPL license. 44 | 45 | 46 | Software that uses `tokenbucket` may be released under *any* license, as long as the source code for `tokenbucket` (including any modifications) are made available under the LGPLv3 license. 47 | 48 | You do not need to release the rest of the software under the LGPL, or any free/open-source license, for that matter (though we would encourage you to do so!). 49 | -------------------------------------------------------------------------------- /vendor/github.com/ChimeraCoder/tokenbucket/README.md: -------------------------------------------------------------------------------- 1 | README -------------------------------------------------------------------------------- /vendor/github.com/ChimeraCoder/tokenbucket/tokenbucket.go: -------------------------------------------------------------------------------- 1 | package tokenbucket 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type Bucket struct { 9 | capacity int64 10 | tokens chan struct{} 11 | rate time.Duration // Add a token to the bucket every 1/r units of time 12 | rateMutex sync.Mutex 13 | } 14 | 15 | func NewBucket(rate time.Duration, capacity int64) *Bucket { 16 | 17 | //A bucket is simply a channel with a buffer representing the maximum size 18 | tokens := make(chan struct{}, capacity) 19 | 20 | b := &Bucket{capacity, tokens, rate, sync.Mutex{}} 21 | 22 | //Set off a function that will continuously add tokens to the bucket 23 | go func(b *Bucket) { 24 | ticker := time.NewTicker(rate) 25 | for _ = range ticker.C { 26 | b.tokens <- struct{}{} 27 | } 28 | }(b) 29 | 30 | return b 31 | } 32 | 33 | func (b *Bucket) GetRate() time.Duration { 34 | b.rateMutex.Lock() 35 | tmp := b.rate 36 | b.rateMutex.Unlock() 37 | return tmp 38 | } 39 | 40 | func (b *Bucket) SetRate(rate time.Duration) { 41 | b.rateMutex.Lock() 42 | b.rate = rate 43 | b.rateMutex.Unlock() 44 | } 45 | 46 | //AddTokens manually adds n tokens to the bucket 47 | func (b *Bucket) AddToken(n int64) { 48 | } 49 | 50 | func (b *Bucket) withdrawTokens(n int64) error { 51 | for i := int64(0); i < n; i++ { 52 | <-b.tokens 53 | } 54 | return nil 55 | } 56 | 57 | func (b *Bucket) SpendToken(n int64) <-chan error { 58 | // Default to spending a single token 59 | if n < 0 { 60 | n = 1 61 | } 62 | 63 | c := make(chan error) 64 | go func(b *Bucket, n int64, c chan error) { 65 | c <- b.withdrawTokens(n) 66 | close(c) 67 | return 68 | }(b, n, c) 69 | 70 | return c 71 | } 72 | 73 | // Drain will empty all tokens in the bucket 74 | // If the tokens are being added too quickly (if the rate is too fast) 75 | // this will never drain 76 | func (b *Bucket) Drain() error { 77 | // TODO replace this with a more solid approach (such as replacing the channel altogether) 78 | for { 79 | select { 80 | case _ = <-b.tokens: 81 | continue 82 | default: 83 | return nil 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /vendor/github.com/ChimeraCoder/tokenbucket/tokenbucket_test.go: -------------------------------------------------------------------------------- 1 | package tokenbucket_test 2 | 3 | import ( 4 | "github.com/ChimeraCoder/tokenbucket" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func Example_BucketUse() { 10 | // Allow a new action every 5 seconds, with a maximum of 3 "in the bank" 11 | bucket := tokenbucket.NewBucket(5*time.Second, 3) 12 | 13 | // To perform a regulated action, we must spend a token 14 | // RegulatedAction will not be performed until the bucket contains enough tokens 15 | <-bucket.SpendToken(1) 16 | RegulatedAction() 17 | } 18 | 19 | // RegulatedAction represents some function that is rate-limited, monitored, 20 | // or otherwise regulated 21 | func RegulatedAction() { 22 | // Some expensive action goes on here 23 | } 24 | 25 | // Test that a bucket that is full does not block execution 26 | func Test_BucketBuffering(t *testing.T) { 27 | // Create a bucket with capacity 3, that adds tokens every 4 seconds 28 | const RATE = 4 * time.Second 29 | const CAPACITY = 3 30 | const ERROR = 500 * time.Millisecond 31 | b := tokenbucket.NewBucket(RATE, CAPACITY) 32 | 33 | // Allow the bucket enough time to fill to capacity 34 | time.Sleep(CAPACITY * RATE) 35 | 36 | // Check that we can empty the bucket without wasting any time 37 | before := time.Now() 38 | <-b.SpendToken(1) 39 | <-b.SpendToken(1) 40 | <-b.SpendToken(1) 41 | after := time.Now() 42 | 43 | if diff := after.Sub(before); diff > RATE { 44 | t.Errorf("Waited %d seconds, though this should have been nearly instantaneous", diff) 45 | } 46 | } 47 | 48 | // Test that a bucket that is empty blocks execution for the correct amount of time 49 | func Test_BucketCreation(t *testing.T) { 50 | // Create a bucket with capacity 3, that adds tokens every 4 seconds 51 | const RATE = 4 * time.Second 52 | const CAPACITY = 3 53 | const ERROR = 500 * time.Millisecond 54 | const EXPECTED_DURATION = RATE * CAPACITY 55 | 56 | b := tokenbucket.NewBucket(RATE, CAPACITY) 57 | 58 | // Ensure that the bucket is empty 59 | <-b.SpendToken(1) 60 | <-b.SpendToken(1) 61 | <-b.SpendToken(1) 62 | <-b.SpendToken(1) 63 | 64 | // Spending three times on an empty bucket should take 12 seconds 65 | // (Take the average across three, due to imprecision/scheduling) 66 | before := time.Now() 67 | <-b.SpendToken(1) 68 | <-b.SpendToken(1) 69 | <-b.SpendToken(1) 70 | after := time.Now() 71 | 72 | lower := EXPECTED_DURATION - ERROR 73 | upper := EXPECTED_DURATION + ERROR 74 | if diff := after.Sub(before); diff < lower || diff > upper { 75 | t.Errorf("Waited %s seconds, though really should have waited between %s and %s", diff.String(), lower.String(), upper.String()) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /vendor/github.com/azr/backoff/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /vendor/github.com/azr/backoff/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 1.3.3 3 | -------------------------------------------------------------------------------- /vendor/github.com/azr/backoff/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Cenk Altı 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /vendor/github.com/azr/backoff/README.md: -------------------------------------------------------------------------------- 1 | # backoff 2 | 3 | [![GoDoc](https://godoc.org/github.com/azr/backoff?status.png)](https://godoc.org/github.com/azr/backoff) 4 | [![Build Status](https://travis-ci.org/azr/backoff.png)](https://travis-ci.org/azr/backoff) 5 | 6 | This is a fork from the awesome [cenkalti/backoff](github.com/cenkalti/backoff) which is a go port from 7 | [google-http-java-client](https://code.google.com/p/google-http-java-client/wiki/ExponentialBackoff). 8 | 9 | This BackOff sleeps upon BackOff() and calculates its next backoff time instead of returning the duration to sleep. 10 | 11 | [Exponential backoff](http://en.wikipedia.org/wiki/Exponential_backoff) 12 | is an algorithm that uses feedback to multiplicatively decrease the rate of some process, 13 | in order to gradually find an acceptable rate. 14 | The retries exponentially increase and stop increasing when a certain threshold is met. 15 | 16 | 17 | 18 | ## Install 19 | 20 | ```bash 21 | go get github.com/azr/backoff 22 | ``` 23 | -------------------------------------------------------------------------------- /vendor/github.com/azr/backoff/backoff.go: -------------------------------------------------------------------------------- 1 | //Package backoff helps you at backing off ! 2 | // 3 | //It was forked from github.com/cenkalti/backoff which is awesome. 4 | // 5 | //This BackOff sleeps upon BackOff() and calculates its next backoff time instead of returning the duration to sleep. 6 | package backoff 7 | 8 | import "time" 9 | 10 | // Interface interface to use after a retryable operation failed. 11 | // A Interface.BackOff sleeps. 12 | type Interface interface { 13 | // Example usage: 14 | // 15 | // for ;; { 16 | // err, canRetry := somethingThatCanFail() 17 | // if err != nil && canRetry { 18 | // backoffer.Backoff() 19 | // } 20 | // } 21 | BackOff() 22 | 23 | // Reset to initial state. 24 | Reset() 25 | } 26 | 27 | // ZeroBackOff is a fixed back-off policy whose back-off time is always zero, 28 | // meaning that the operation is retried immediately without waiting. 29 | type ZeroBackOff struct{} 30 | 31 | var _ Interface = (*ZeroBackOff)(nil) 32 | 33 | func (b *ZeroBackOff) Reset() {} 34 | 35 | func (b *ZeroBackOff) BackOff() {} 36 | 37 | type ConstantBackOff struct { 38 | Interval time.Duration 39 | } 40 | 41 | var _ Interface = (*ConstantBackOff)(nil) 42 | 43 | func (b *ConstantBackOff) Reset() {} 44 | 45 | func (b *ConstantBackOff) BackOff() { 46 | time.Sleep(b.Interval) 47 | } 48 | 49 | func NewConstant(d time.Duration) *ConstantBackOff { 50 | return &ConstantBackOff{Interval: d} 51 | } 52 | -------------------------------------------------------------------------------- /vendor/github.com/azr/backoff/backoff_test.go: -------------------------------------------------------------------------------- 1 | package backoff 2 | 3 | import ( 4 | "time" 5 | 6 | "testing" 7 | ) 8 | 9 | func TestNextBackOffMillis(t *testing.T) { 10 | new(ZeroBackOff).BackOff() 11 | } 12 | 13 | func TestConstantBackOff(t *testing.T) { 14 | backoff := NewConstant(time.Second) 15 | if backoff.Interval != time.Second { 16 | t.Error("invalid interval") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /vendor/github.com/azr/backoff/example_test.go: -------------------------------------------------------------------------------- 1 | package backoff_test 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/azr/backoff" 8 | ) 9 | 10 | func ExampleNewExponential_defaultWaitingIntervals() { 11 | exp := backoff.NewExponential() 12 | 13 | for i := 0; i < 25; i++ { 14 | d := exp.GetSleepTime() 15 | fmt.Printf("Random duration was %2.2fs, interval: %2.2fs in [ %2.2fs , %2.2fs ]\n", 16 | d.Seconds(), 17 | exp.Inverval().Seconds(), 18 | (exp.Inverval() - time.Duration(exp.RandomizationFactor*float64(exp.Inverval()))).Seconds(), 19 | (exp.Inverval() + time.Duration(exp.RandomizationFactor*float64(exp.Inverval()))).Seconds(), 20 | ) 21 | exp.IncrementCurrentInterval() 22 | // exp.BackOff() would have executed time.Sleep(exp.GetSleepTime()) and exp.IncrementCurrentInterval() 23 | } 24 | // Output: 25 | // Random duration was 0.51s, interval: 0.50s in [ 0.25s , 0.75s ] 26 | // Random duration was 0.99s, interval: 0.75s in [ 0.38s , 1.12s ] 27 | // Random duration was 0.80s, interval: 1.12s in [ 0.56s , 1.69s ] 28 | // Random duration was 1.49s, interval: 1.69s in [ 0.84s , 2.53s ] 29 | // Random duration was 2.07s, interval: 2.53s in [ 1.27s , 3.80s ] 30 | // Random duration was 3.68s, interval: 3.80s in [ 1.90s , 5.70s ] 31 | // Random duration was 4.46s, interval: 5.70s in [ 2.85s , 8.54s ] 32 | // Random duration was 6.78s, interval: 8.54s in [ 4.27s , 12.81s ] 33 | // Random duration was 15.11s, interval: 12.81s in [ 6.41s , 19.22s ] 34 | // Random duration was 13.81s, interval: 19.22s in [ 9.61s , 28.83s ] 35 | // Random duration was 20.27s, interval: 28.83s in [ 14.42s , 43.25s ] 36 | // Random duration was 37.23s, interval: 43.25s in [ 21.62s , 64.87s ] 37 | // Random duration was 64.24s, interval: 60.00s in [ 30.00s , 90.00s ] 38 | // Random duration was 81.75s, interval: 60.00s in [ 30.00s , 90.00s ] 39 | // Random duration was 47.59s, interval: 60.00s in [ 30.00s , 90.00s ] 40 | // Random duration was 47.82s, interval: 60.00s in [ 30.00s , 90.00s ] 41 | // Random duration was 75.15s, interval: 60.00s in [ 30.00s , 90.00s ] 42 | // Random duration was 42.39s, interval: 60.00s in [ 30.00s , 90.00s ] 43 | // Random duration was 81.92s, interval: 60.00s in [ 30.00s , 90.00s ] 44 | // Random duration was 71.80s, interval: 60.00s in [ 30.00s , 90.00s ] 45 | // Random duration was 61.43s, interval: 60.00s in [ 30.00s , 90.00s ] 46 | // Random duration was 31.70s, interval: 60.00s in [ 30.00s , 90.00s ] 47 | // Random duration was 39.50s, interval: 60.00s in [ 30.00s , 90.00s ] 48 | // Random duration was 66.44s, interval: 60.00s in [ 30.00s , 90.00s ] 49 | // Random duration was 88.51s, interval: 60.00s in [ 30.00s , 90.00s ] 50 | } 51 | -------------------------------------------------------------------------------- /vendor/github.com/azr/backoff/exponential.go: -------------------------------------------------------------------------------- 1 | package backoff 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | /* 9 | ExponentialBackOff is an implementation of BackOff that increases 10 | it's back off period for each retry attempt using a randomization function 11 | that grows exponentially. 12 | Backoff() time is calculated using the following formula: 13 | randomized_interval = 14 | retry_interval * (random value in range [1 - randomization_factor, 1 + randomization_factor]) 15 | In other words BackOff() will sleep for times between the randomization factor 16 | percentage below and above the retry interval. 17 | For example, using 2 seconds as the base retry interval and 0.5 as the 18 | randomization factor, the actual back off period used in the next retry 19 | attempt will be between 1 and 3 seconds. 20 | 21 | Note: max_interval caps the retry_interval and not the randomized_interval. 22 | 23 | Example: The default retry_interval is .5 seconds, default randomization_factor 24 | is 0.5, default multiplier is 1.5 and the max_interval is set to 25 seconds. 25 | For 12 tries the sequence will sleep (values in seconds) (output from ExampleExpBackOffTimes) : 26 | 27 | request# retry_interval randomized_interval 28 | 29 | 1 0.5 [0.25, 0.75] 30 | 2 0.75 [0.375, 1.125] 31 | 3 1.125 [0.562, 1.687] 32 | 4 1.687 [0.8435, 2.53] 33 | 5 2.53 [1.265, 3.795] 34 | 6 3.795 [1.897, 5.692] 35 | 7 5.692 [2.846, 8.538] 36 | 8 8.538 [4.269, 12.807] 37 | 9 12.807 [6.403, 19.210] 38 | 10 19.22 [9.611, 28.833] 39 | 11 25 [12.5, 37.5] 40 | 12 25 [12.5, 37.5] 41 | Implementation is not thread-safe. 42 | */ 43 | type ExponentialBackOff struct { 44 | InitialInterval time.Duration 45 | currentInterval time.Duration 46 | MaxInterval time.Duration 47 | 48 | RandomizationFactor float64 49 | Multiplier float64 50 | } 51 | 52 | // Default values for ExponentialBackOff. 53 | const ( 54 | DefaultInitialInterval = 500 * time.Millisecond 55 | DefaultRandomizationFactor = 0.5 56 | DefaultMultiplier = 1.5 57 | DefaultMaxInterval = 60 * time.Second 58 | ) 59 | 60 | // NewExponential creates an instance of ExponentialBackOff using default values. 61 | func NewExponential() *ExponentialBackOff { 62 | b := &ExponentialBackOff{ 63 | InitialInterval: DefaultInitialInterval, 64 | RandomizationFactor: DefaultRandomizationFactor, 65 | Multiplier: DefaultMultiplier, 66 | MaxInterval: DefaultMaxInterval, 67 | currentInterval: DefaultInitialInterval, 68 | } 69 | b.Reset() 70 | return b 71 | } 72 | 73 | // Reset the interval back to the initial retry interval and restarts the timer. 74 | func (b *ExponentialBackOff) Reset() { 75 | b.currentInterval = b.InitialInterval 76 | } 77 | 78 | func (b *ExponentialBackOff) GetSleepTime() time.Duration { 79 | return getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval) 80 | } 81 | 82 | func (b *ExponentialBackOff) BackOff() { 83 | time.Sleep(b.GetSleepTime()) 84 | 85 | b.IncrementCurrentInterval() 86 | } 87 | 88 | // Increments the current interval by multiplying it with the multiplier. 89 | func (b *ExponentialBackOff) IncrementCurrentInterval() { 90 | // Check for overflow, if overflow is detected set the current interval to the max interval. 91 | if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier { 92 | b.currentInterval = b.MaxInterval 93 | } else { 94 | b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier) 95 | } 96 | } 97 | 98 | func (b *ExponentialBackOff) Inverval() time.Duration { 99 | return b.currentInterval 100 | } 101 | 102 | // Returns a random value from the interval: 103 | // [randomizationFactor * currentInterval, randomizationFactor * currentInterval]. 104 | func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration { 105 | var delta = randomizationFactor * float64(currentInterval) 106 | var minInterval = float64(currentInterval) - delta 107 | var maxInterval = float64(currentInterval) + delta 108 | // Get a random value from the range [minInterval, maxInterval]. 109 | // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then 110 | // we want a 33% chance for selecting either 1, 2 or 3. 111 | return time.Duration(minInterval + (random * (maxInterval - minInterval + 1))) 112 | } 113 | -------------------------------------------------------------------------------- /vendor/github.com/azr/backoff/exponential_test.go: -------------------------------------------------------------------------------- 1 | package backoff 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestBackOff(t *testing.T) { 10 | var ( 11 | testInitialInterval = 500 * time.Millisecond 12 | testRandomizationFactor = 0.1 13 | testMultiplier = 2.0 14 | testMaxInterval = 5 * time.Second 15 | ) 16 | 17 | exp := NewExponential() 18 | exp.InitialInterval = testInitialInterval 19 | exp.RandomizationFactor = testRandomizationFactor 20 | exp.Multiplier = testMultiplier 21 | exp.MaxInterval = testMaxInterval 22 | exp.Reset() 23 | 24 | var expectedResults = []time.Duration{500, 1000, 2000, 4000, 5000, 5000, 5000, 5000, 5000, 5000} 25 | for i, d := range expectedResults { 26 | expectedResults[i] = d * time.Millisecond 27 | } 28 | 29 | for _, expected := range expectedResults { 30 | assertEquals(t, expected, exp.currentInterval) 31 | // Assert that the next back off falls in the expected range. 32 | var minInterval = expected - time.Duration(testRandomizationFactor*float64(expected)) 33 | var maxInterval = expected + time.Duration(testRandomizationFactor*float64(expected)) 34 | var actualInterval = exp.GetSleepTime() 35 | if !(minInterval <= actualInterval && actualInterval <= maxInterval) { 36 | t.Error("error") 37 | } 38 | exp.IncrementCurrentInterval() 39 | } 40 | } 41 | 42 | func TestGetRandomizedInterval(t *testing.T) { 43 | // 33% chance of being 1. 44 | assertEquals(t, 1, getRandomValueFromInterval(0.5, 0, 2)) 45 | assertEquals(t, 1, getRandomValueFromInterval(0.5, 0.33, 2)) 46 | // 33% chance of being 2. 47 | assertEquals(t, 2, getRandomValueFromInterval(0.5, 0.34, 2)) 48 | assertEquals(t, 2, getRandomValueFromInterval(0.5, 0.66, 2)) 49 | // 33% chance of being 3. 50 | assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.67, 2)) 51 | assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.99, 2)) 52 | } 53 | 54 | type TestClock struct { 55 | i time.Duration 56 | start time.Time 57 | } 58 | 59 | func (c *TestClock) Now() time.Time { 60 | t := c.start.Add(c.i) 61 | c.i += time.Second 62 | return t 63 | } 64 | 65 | func TestBackOffOverflow(t *testing.T) { 66 | var ( 67 | testInitialInterval time.Duration = math.MaxInt64 / 2 68 | testMaxInterval time.Duration = math.MaxInt64 69 | testMultiplier float64 = 2.1 70 | ) 71 | 72 | exp := NewExponential() 73 | exp.InitialInterval = testInitialInterval 74 | exp.Multiplier = testMultiplier 75 | exp.MaxInterval = testMaxInterval 76 | exp.Reset() 77 | 78 | exp.IncrementCurrentInterval() 79 | // Assert that when an overflow is possible the current varerval time.Duration is set to the max varerval time.Duration . 80 | assertEquals(t, testMaxInterval, exp.currentInterval) 81 | } 82 | 83 | func assertEquals(t *testing.T, expected, value time.Duration) { 84 | if expected != value { 85 | t.Errorf("got: %d, expected: %d", value, expected) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /vendor/github.com/azr/backoff/linear.go: -------------------------------------------------------------------------------- 1 | package backoff 2 | 3 | // LinearBackOff is a back-off policy whose back-off time is multiplied by mult and incremented by incr 4 | // each time it is called. 5 | // mult can be one ;). 6 | import "time" 7 | 8 | // grows linearly until 9 | type LinearBackOff struct { 10 | InitialInterval time.Duration 11 | Multiplier float64 12 | Increment time.Duration 13 | MaxInterval time.Duration 14 | currentInterval time.Duration 15 | } 16 | 17 | var _ Interface = (*LinearBackOff)(nil) 18 | 19 | func NewLinear(from, to, incr time.Duration, mult float64) *LinearBackOff { 20 | return &LinearBackOff{ 21 | InitialInterval: from, 22 | MaxInterval: to, 23 | currentInterval: from, 24 | Increment: incr, 25 | Multiplier: mult, 26 | } 27 | } 28 | 29 | func (lb *LinearBackOff) Reset() { 30 | lb.currentInterval = lb.InitialInterval 31 | } 32 | 33 | func (lb *LinearBackOff) increment() { 34 | lb.currentInterval = time.Duration(float64(lb.currentInterval) * lb.Multiplier) 35 | lb.currentInterval += lb.Increment 36 | if lb.currentInterval > lb.MaxInterval { 37 | lb.currentInterval = lb.MaxInterval 38 | } 39 | } 40 | 41 | func (lb *LinearBackOff) BackOff() { 42 | time.Sleep(lb.currentInterval) 43 | lb.increment() 44 | } 45 | -------------------------------------------------------------------------------- /vendor/github.com/azr/backoff/linear_test.go: -------------------------------------------------------------------------------- 1 | package backoff 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestLinear(t *testing.T) { 9 | bmult := NewLinear(time.Minute, 10*time.Minute, 0, 4) 10 | 11 | bmult.increment() 12 | 13 | if bmult.currentInterval != time.Minute*4 { 14 | t.Errorf("increment did not work got %s expected %s.", bmult.currentInterval, time.Minute*2) 15 | } 16 | 17 | bincr := NewLinear(time.Minute, 10*time.Minute, time.Minute, 1) 18 | 19 | bincr.increment() 20 | 21 | if bincr.currentInterval != time.Minute*2 { 22 | t.Errorf("increment did not work got %s expected %s.", bincr.currentInterval, time.Minute*2) 23 | } 24 | 25 | bmultincr := NewLinear(time.Minute, 10*time.Minute, time.Minute, 2) 26 | bmultincr.increment() 27 | if bmultincr.currentInterval != time.Minute*3 { 28 | t.Errorf("increment did not work got %s expected %s.", bmultincr.currentInterval, time.Minute*3) 29 | } 30 | 31 | bmultincr.Reset() 32 | 33 | if bmultincr.currentInterval != time.Minute { 34 | t.Errorf("reset did not work got %s expected %s.", bmultincr.currentInterval, time.Minute) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/go-jsonpointer/.gitignore: -------------------------------------------------------------------------------- 1 | #* 2 | *~ 3 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/go-jsonpointer/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Dustin Sallings 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/go-jsonpointer/README.markdown: -------------------------------------------------------------------------------- 1 | # JSON Pointer for go 2 | 3 | This is an implementation of [JSON Pointer](http://tools.ietf.org/html/rfc6901). 4 | 5 | [![Coverage Status](https://coveralls.io/repos/dustin/go-jsonpointer/badge.png?branch=master)](https://coveralls.io/r/dustin/go-jsonpointer?branch=master) 6 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/go-jsonpointer/doc.go: -------------------------------------------------------------------------------- 1 | // Package jsonpointer implements RFC6901 JSON Pointers 2 | package jsonpointer 3 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/go-jsonpointer/map.go: -------------------------------------------------------------------------------- 1 | package jsonpointer 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | // Get the value at the specified path. 9 | func Get(m map[string]interface{}, path string) interface{} { 10 | if path == "" { 11 | return m 12 | } 13 | 14 | parts := strings.Split(path[1:], "/") 15 | var rv interface{} = m 16 | 17 | for _, p := range parts { 18 | switch v := rv.(type) { 19 | case map[string]interface{}: 20 | if strings.Contains(p, "~") { 21 | p = strings.Replace(p, "~1", "/", -1) 22 | p = strings.Replace(p, "~0", "~", -1) 23 | } 24 | rv = v[p] 25 | case []interface{}: 26 | i, err := strconv.Atoi(p) 27 | if err == nil && i < len(v) { 28 | rv = v[i] 29 | } else { 30 | return nil 31 | } 32 | default: 33 | return nil 34 | } 35 | } 36 | 37 | return rv 38 | } 39 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/go-jsonpointer/map_test.go: -------------------------------------------------------------------------------- 1 | package jsonpointer 2 | 3 | import ( 4 | "io/ioutil" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dustin/gojson" 9 | ) 10 | 11 | const objSrc = `{ 12 | "foo": ["bar", "baz"], 13 | "": 0, 14 | "a/b": 1, 15 | "c%d": 2, 16 | "e^f": 3, 17 | "g|h": 4, 18 | "i\\j": 5, 19 | "k\"l": 6, 20 | " ": 7, 21 | "m~n": 8, 22 | "g/n/r": "has slash, will travel", 23 | "g": { "n": {"r": "where's tito?"}} 24 | }` 25 | 26 | var obj = map[string]interface{}{} 27 | 28 | var tests = []struct { 29 | path string 30 | exp interface{} 31 | }{ 32 | {"", obj}, 33 | {"/foo", []interface{}{"bar", "baz"}}, 34 | {"/foo/0", "bar"}, 35 | {"/foo/99", nil}, 36 | {"/foo/0/3", nil}, 37 | {"/", 0.0}, 38 | {"/a~1b", 1.0}, 39 | {"/c%d", 2.0}, 40 | {"/e^f", 3.0}, 41 | {"/g|h", 4.0}, 42 | {"/i\\j", 5.0}, 43 | {"/k\"l", 6.0}, 44 | {"/ ", 7.0}, 45 | {"/m~0n", 8.0}, 46 | {"/g~1n~1r", "has slash, will travel"}, 47 | {"/g/n/r", "where's tito?"}, 48 | } 49 | 50 | func init() { 51 | err := json.Unmarshal([]byte(objSrc), &obj) 52 | if err != nil { 53 | panic(err) 54 | } 55 | } 56 | 57 | func TestPaths(t *testing.T) { 58 | for _, test := range tests { 59 | got := Get(obj, test.path) 60 | if !reflect.DeepEqual(got, test.exp) { 61 | t.Errorf("On %v, expected %+v (%T), got %+v (%T)", 62 | test.path, test.exp, test.exp, got, got) 63 | } else { 64 | t.Logf("Success - got %v for %v", got, test.path) 65 | } 66 | } 67 | } 68 | 69 | func BenchmarkPaths(b *testing.B) { 70 | for i := 0; i < b.N; i++ { 71 | for _, test := range tests { 72 | Get(obj, test.path) 73 | } 74 | } 75 | } 76 | 77 | func BenchmarkParseAndPath(b *testing.B) { 78 | for i := 0; i < b.N; i++ { 79 | for _, test := range tests { 80 | o := map[string]interface{}{} 81 | err := json.Unmarshal([]byte(objSrc), &o) 82 | if err != nil { 83 | b.Fatalf("Error parsing: %v", err) 84 | } 85 | Get(o, test.path) 86 | } 87 | } 88 | } 89 | 90 | var bug3Data = []byte(`{"foo" : "bar"}`) 91 | 92 | func TestFindSpaceBeforeColon(t *testing.T) { 93 | val, err := Find(bug3Data, "/foo") 94 | if err != nil { 95 | t.Fatalf("Failed to find /foo: %v", err) 96 | } 97 | x, ok := json.UnquoteBytes(val) 98 | if !ok { 99 | t.Fatalf("Failed to unquote json bytes from %q", val) 100 | } 101 | if string(x) != "bar" { 102 | t.Fatalf("Expected %q, got %q", "bar", val) 103 | } 104 | } 105 | 106 | func TestListSpaceBeforeColon(t *testing.T) { 107 | ptrs, err := ListPointers(bug3Data) 108 | if err != nil { 109 | t.Fatalf("Error listing pointers: %v", err) 110 | } 111 | if len(ptrs) != 2 || ptrs[0] != "" || ptrs[1] != "/foo" { 112 | t.Fatalf(`Expected ["", "/foo"], got %#v`, ptrs) 113 | } 114 | } 115 | 116 | func TestIndexNotFoundSameAsPropertyNotFound(t *testing.T) { 117 | data, err := ioutil.ReadFile("testdata/357.json") 118 | if err != nil { 119 | t.Fatalf("Error beer-sample brewery 357 data: %v", err) 120 | } 121 | 122 | expectedResult, expectedError := Find(data, "/doesNotExist") 123 | 124 | missingVals := []string{ 125 | "/address/0", 126 | "/address/1", 127 | "/address2/1", 128 | "/address2/2", 129 | "/address3/0", 130 | "/address3/1", 131 | } 132 | 133 | for _, a := range missingVals { 134 | found, err := Find(data, a) 135 | 136 | if !reflect.DeepEqual(err, expectedError) { 137 | t.Errorf("Expected %v at %v, got %v", expectedError, a, err) 138 | } 139 | if !reflect.DeepEqual(expectedResult, found) { 140 | t.Errorf("Expected %v at %v, got %v", expectedResult, a, found) 141 | } 142 | } 143 | } 144 | 145 | const bug822src = `{ 146 | "foo": ["bar", "baz"], 147 | "": 0, 148 | "a/b": 1, 149 | "c%d": 2, 150 | "e^f": 3, 151 | "g|h": 4, 152 | "i\\j": 5, 153 | "k\"l": 6, 154 | "k2": {}, 155 | " ": 7, 156 | "m~n": 8, 157 | "g/n/r": "has slash, will travel", 158 | "g": { "n": {"r": "where's tito?"}}, 159 | "h": {} 160 | }` 161 | 162 | func TestListEmptyObjectPanic822(t *testing.T) { 163 | ptrs, err := ListPointers([]byte(bug822src)) 164 | if err != nil { 165 | t.Fatalf("Error parsing: %v", err) 166 | } 167 | t.Logf("Got pointers: %v", ptrs) 168 | } 169 | 170 | func TestFindEmptyObjectPanic823(t *testing.T) { 171 | for _, test := range tests { 172 | _, err := Find([]byte(bug822src), test.path) 173 | if err != nil { 174 | t.Errorf("Error looking for %v: %v", test.path, err) 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/go-jsonpointer/ptrtool/ptrtool.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | 11 | "github.com/dustin/go-jsonpointer" 12 | ) 13 | 14 | func listPointers(d []byte) { 15 | l, err := jsonpointer.ListPointers(d) 16 | if err != nil { 17 | log.Fatalf("Error listing pointers: %v", err) 18 | } 19 | for _, p := range l { 20 | fmt.Println(p) 21 | } 22 | 23 | } 24 | 25 | func selectItems(d []byte, pointers []string) { 26 | m, err := jsonpointer.FindMany(d, pointers) 27 | if err != nil { 28 | log.Fatalf("Error finding pointers: %v", err) 29 | } 30 | for k, v := range m { 31 | b := &bytes.Buffer{} 32 | json.Indent(b, v, "", " ") 33 | fmt.Printf("%v\n%s\n\n", k, b) 34 | } 35 | } 36 | 37 | func main() { 38 | d, err := ioutil.ReadAll(os.Stdin) 39 | if err != nil { 40 | log.Fatalf("Error reading json from stdin: %v", err) 41 | } 42 | if len(os.Args) == 1 { 43 | listPointers(d) 44 | } else { 45 | selectItems(d, os.Args[1:]) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/go-jsonpointer/reflect.go: -------------------------------------------------------------------------------- 1 | package jsonpointer 2 | 3 | import ( 4 | "reflect" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // Reflect gets the value at the specified path from a struct. 10 | func Reflect(o interface{}, path string) interface{} { 11 | if path == "" { 12 | return o 13 | } 14 | 15 | parts := parsePointer(path) 16 | var rv interface{} = o 17 | 18 | OUTER: 19 | for _, p := range parts { 20 | val := reflect.ValueOf(rv) 21 | if val.Kind() == reflect.Ptr { 22 | val = val.Elem() 23 | } 24 | 25 | if val.Kind() == reflect.Struct { 26 | typ := val.Type() 27 | for i := 0; i < typ.NumField(); i++ { 28 | sf := typ.Field(i) 29 | tag := sf.Tag.Get("json") 30 | name := parseJSONTagName(tag) 31 | if (name != "" && name == p) || sf.Name == p { 32 | rv = val.Field(i).Interface() 33 | continue OUTER 34 | } 35 | } 36 | // Found no matching field. 37 | return nil 38 | } else if val.Kind() == reflect.Map { 39 | // our pointer always gives us a string key 40 | // here we try to convert it into the correct type 41 | mapKey, canConvert := makeMapKeyFromString(val.Type().Key(), p) 42 | if canConvert { 43 | field := val.MapIndex(mapKey) 44 | if field.IsValid() { 45 | rv = field.Interface() 46 | } else { 47 | return nil 48 | } 49 | } else { 50 | return nil 51 | } 52 | } else if val.Kind() == reflect.Slice || val.Kind() == reflect.Array { 53 | i, err := strconv.Atoi(p) 54 | if err == nil && i < val.Len() { 55 | rv = val.Index(i).Interface() 56 | } else { 57 | return nil 58 | } 59 | } else { 60 | return nil 61 | } 62 | } 63 | 64 | return rv 65 | } 66 | 67 | // ReflectListPointers lists all possible pointers from the given struct. 68 | func ReflectListPointers(o interface{}) ([]string, error) { 69 | return reflectListPointersRecursive(o, ""), nil 70 | } 71 | 72 | func reflectListPointersRecursive(o interface{}, prefix string) []string { 73 | rv := []string{prefix + ""} 74 | 75 | val := reflect.ValueOf(o) 76 | if val.Kind() == reflect.Ptr { 77 | val = val.Elem() 78 | } 79 | 80 | if val.Kind() == reflect.Struct { 81 | 82 | typ := val.Type() 83 | for i := 0; i < typ.NumField(); i++ { 84 | child := val.Field(i).Interface() 85 | sf := typ.Field(i) 86 | tag := sf.Tag.Get("json") 87 | name := parseJSONTagName(tag) 88 | if name != "" { 89 | // use the tag name 90 | childReults := reflectListPointersRecursive(child, prefix+encodePointer([]string{name})) 91 | rv = append(rv, childReults...) 92 | } else { 93 | // use the original field name 94 | childResults := reflectListPointersRecursive(child, prefix+encodePointer([]string{sf.Name})) 95 | rv = append(rv, childResults...) 96 | } 97 | } 98 | 99 | } else if val.Kind() == reflect.Map { 100 | for _, k := range val.MapKeys() { 101 | child := val.MapIndex(k).Interface() 102 | mapKeyName := makeMapKeyName(k) 103 | childReults := reflectListPointersRecursive(child, prefix+encodePointer([]string{mapKeyName})) 104 | rv = append(rv, childReults...) 105 | } 106 | } else if val.Kind() == reflect.Slice || val.Kind() == reflect.Array { 107 | for i := 0; i < val.Len(); i++ { 108 | child := val.Index(i).Interface() 109 | childResults := reflectListPointersRecursive(child, prefix+encodePointer([]string{strconv.Itoa(i)})) 110 | rv = append(rv, childResults...) 111 | } 112 | } 113 | return rv 114 | } 115 | 116 | // makeMapKeyName takes a map key value and creates a string representation 117 | func makeMapKeyName(v reflect.Value) string { 118 | switch v.Kind() { 119 | case reflect.Float32, reflect.Float64: 120 | fv := v.Float() 121 | return strconv.FormatFloat(fv, 'f', -1, v.Type().Bits()) 122 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 123 | iv := v.Int() 124 | return strconv.FormatInt(iv, 10) 125 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 126 | iv := v.Uint() 127 | return strconv.FormatUint(iv, 10) 128 | default: 129 | return v.String() 130 | } 131 | } 132 | 133 | // makeMapKeyFromString takes the key type for a map, and a string 134 | // representing the key, it then tries to convert the string 135 | // representation into a value of the correct type. 136 | func makeMapKeyFromString(mapKeyType reflect.Type, pointer string) (reflect.Value, bool) { 137 | valp := reflect.New(mapKeyType) 138 | val := reflect.Indirect(valp) 139 | switch mapKeyType.Kind() { 140 | case reflect.String: 141 | return reflect.ValueOf(pointer), true 142 | case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: 143 | iv, err := strconv.ParseInt(pointer, 10, mapKeyType.Bits()) 144 | if err == nil { 145 | val.SetInt(iv) 146 | return val, true 147 | } 148 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: 149 | iv, err := strconv.ParseUint(pointer, 10, mapKeyType.Bits()) 150 | if err == nil { 151 | val.SetUint(iv) 152 | return val, true 153 | } 154 | case reflect.Float32, reflect.Float64: 155 | fv, err := strconv.ParseFloat(pointer, mapKeyType.Bits()) 156 | if err == nil { 157 | val.SetFloat(fv) 158 | return val, true 159 | } 160 | } 161 | 162 | return reflect.ValueOf(nil), false 163 | } 164 | 165 | // parseJSONTagName extracts the JSON field name from a struct tag 166 | func parseJSONTagName(tag string) string { 167 | if idx := strings.Index(tag, ","); idx != -1 { 168 | return tag[:idx] 169 | } 170 | return tag 171 | } 172 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/go-jsonpointer/reflect_test.go: -------------------------------------------------------------------------------- 1 | package jsonpointer 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type address struct { 9 | Street string `json:"street"` 10 | Zip string 11 | } 12 | 13 | type person struct { 14 | Name string `json:"name,omitempty"` 15 | Twitter string 16 | Aliases []string `json:"aliases"` 17 | Addresses []*address `json:"addresses"` 18 | NameTildeContained string `json:"name~contained"` 19 | NameSlashContained string `json:"name/contained"` 20 | AnActualArray [4]int 21 | NestedMap map[string]float64 `json:"nestedmap"` 22 | MapIntKey map[int]string 23 | MapUintKey map[uint]string 24 | MapFloatKey map[float64]string 25 | } 26 | 27 | var input = &person{ 28 | Name: "marty", 29 | Twitter: "mschoch", 30 | Aliases: []string{ 31 | "jabroni", 32 | "beer", 33 | }, 34 | Addresses: []*address{ 35 | &address{ 36 | Street: "123 Sesame St.", 37 | Zip: "99099", 38 | }, 39 | }, 40 | NameTildeContained: "yessir", 41 | NameSlashContained: "nosir", 42 | AnActualArray: [4]int{0, 1, 2, 3}, 43 | NestedMap: map[string]float64{ 44 | "pi": 3.14, 45 | "back/saidhe": 2.71, 46 | "till~duh": 1.41, 47 | }, 48 | MapIntKey: map[int]string{ 49 | 1: "one", 50 | 2: "two", 51 | }, 52 | MapUintKey: map[uint]string{ 53 | 3: "three", 54 | 4: "four", 55 | }, 56 | MapFloatKey: map[float64]string{ 57 | 3.14: "pi", 58 | 4.15: "notpi", 59 | }, 60 | } 61 | 62 | func benchReflect(b *testing.B, path string) { 63 | for i := 0; i < b.N; i++ { 64 | if Reflect(input, path) == nil { 65 | b.FailNow() 66 | } 67 | } 68 | } 69 | 70 | func BenchmarkReflectRoot(b *testing.B) { 71 | benchReflect(b, "") 72 | } 73 | 74 | func BenchmarkReflectToplevelExact(b *testing.B) { 75 | benchReflect(b, "/Twitter") 76 | } 77 | 78 | func BenchmarkReflectToplevelTagged(b *testing.B) { 79 | benchReflect(b, "/Name") 80 | } 81 | 82 | func BenchmarkReflectToplevelTaggedLower(b *testing.B) { 83 | benchReflect(b, "/name") 84 | } 85 | 86 | func BenchmarkReflectDeep(b *testing.B) { 87 | benchReflect(b, "/addresses/0/Zip") 88 | } 89 | 90 | func BenchmarkReflectSlash(b *testing.B) { 91 | benchReflect(b, "/name~1contained") 92 | } 93 | 94 | func BenchmarkReflectTilde(b *testing.B) { 95 | benchReflect(b, "/name~0contained") 96 | } 97 | 98 | func compareStringArrayIgnoringOrder(a, b []string) bool { 99 | if len(a) != len(b) { 100 | return false 101 | } 102 | tmp := make(map[string]bool, len(a)) 103 | for _, av := range a { 104 | tmp[av] = true 105 | } 106 | for _, bv := range b { 107 | if tmp[bv] != true { 108 | return false 109 | } 110 | } 111 | return true 112 | } 113 | 114 | func TestReflectListPointers(t *testing.T) { 115 | pointers, err := ReflectListPointers(input) 116 | if err != nil { 117 | t.Fatal(err) 118 | } 119 | expect := []string{"", "/name", "/Twitter", "/aliases", 120 | "/aliases/0", "/aliases/1", "/addresses", "/addresses/0", 121 | "/addresses/0/street", "/addresses/0/Zip", 122 | "/name~0contained", "/name~1contained", "/AnActualArray", 123 | "/AnActualArray/0", "/AnActualArray/1", "/AnActualArray/2", 124 | "/AnActualArray/3", "/nestedmap", "/nestedmap/pi", 125 | "/nestedmap/back~1saidhe", "/nestedmap/till~0duh", 126 | "/MapIntKey", "/MapIntKey/1", "/MapIntKey/2", "/MapUintKey", 127 | "/MapUintKey/3", "/MapUintKey/4", "/MapFloatKey", 128 | "/MapFloatKey/3.14", "/MapFloatKey/4.15"} 129 | if !compareStringArrayIgnoringOrder(expect, pointers) { 130 | t.Fatalf("expected %#v, got %#v", expect, pointers) 131 | } 132 | } 133 | 134 | func TestReflectNonObjectOrSlice(t *testing.T) { 135 | got := Reflect(36, "/test") 136 | if got != nil { 137 | t.Errorf("expected nil, got %#v", got) 138 | } 139 | } 140 | 141 | type structThatCanBeUsedAsKey struct { 142 | name string 143 | domain string 144 | } 145 | 146 | func TestReflectMapThatWontWork(t *testing.T) { 147 | 148 | amapthatwontwork := map[structThatCanBeUsedAsKey]string{} 149 | akey := structThatCanBeUsedAsKey{name: "marty", domain: "couchbase"} 150 | amapthatwontwork[akey] = "verycontrived" 151 | 152 | got := Reflect(amapthatwontwork, "/anykey") 153 | if got != nil { 154 | t.Errorf("expected nil, got %#v", got) 155 | } 156 | } 157 | 158 | func TestReflect(t *testing.T) { 159 | 160 | tests := []struct { 161 | path string 162 | exp interface{} 163 | }{ 164 | { 165 | path: "", 166 | exp: input, 167 | }, 168 | { 169 | path: "/", exp: nil, 170 | }, 171 | { 172 | path: "/name", 173 | exp: "marty", 174 | }, 175 | { 176 | path: "/Name", 177 | exp: "marty", 178 | }, 179 | { 180 | path: "/Twitter", 181 | exp: "mschoch", 182 | }, 183 | { 184 | path: "/aliases/0", 185 | exp: "jabroni", 186 | }, 187 | { 188 | path: "/Aliases/0", 189 | exp: "jabroni", 190 | }, 191 | { 192 | path: "/addresses/0/street", 193 | exp: "123 Sesame St.", 194 | }, 195 | { 196 | path: "/addresses/4/street", 197 | exp: nil, 198 | }, 199 | { 200 | path: "/doesntexist", 201 | exp: nil, 202 | }, 203 | { 204 | path: "/does/not/exit", 205 | exp: nil, 206 | }, 207 | { 208 | path: "/doesntexist/7", 209 | exp: nil, 210 | }, 211 | { 212 | path: "/name~0contained", 213 | exp: "yessir", 214 | }, 215 | { 216 | path: "/name~1contained", 217 | exp: "nosir", 218 | }, 219 | { 220 | path: "/AnActualArray/2", 221 | exp: 2, 222 | }, 223 | { 224 | path: "/AnActualArray/5", 225 | exp: nil, 226 | }, 227 | { 228 | path: "/nestedmap/pi", 229 | exp: 3.14, 230 | }, 231 | { 232 | path: "/nestedmap/back~1saidhe", 233 | exp: 2.71, 234 | }, 235 | { 236 | path: "/nestedmap/till~0duh", 237 | exp: 1.41, 238 | }, 239 | { 240 | path: "/MapIntKey/1", 241 | exp: "one", 242 | }, 243 | { 244 | path: "/MapUintKey/3", 245 | exp: "three", 246 | }, 247 | { 248 | path: "/MapFloatKey/3.14", 249 | exp: "pi", 250 | }, 251 | { 252 | path: "/MapFloatKey/4.0", 253 | exp: nil, 254 | }, 255 | } 256 | 257 | for _, test := range tests { 258 | output := Reflect(input, test.path) 259 | if !reflect.DeepEqual(output, test.exp) { 260 | t.Errorf("Expected %#v for %q, got %#v", test.exp, test.path, output) 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/go-jsonpointer/testdata/357.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "357", 3 | "city": "", 4 | "state": "", 5 | "code": "", 6 | "country": "", 7 | "phone": "", 8 | "website": "", 9 | "type": "brewery", 10 | "updated": "2010-07-22 20:00:20", 11 | "description": "", 12 | "address": [], 13 | "address2": ["x"], 14 | "address3": [ ] 15 | } 16 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/go-jsonpointer/testdata/code.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/anaconda/fba449f7b405a61af4f8a9246bd557f9625bd7be/vendor/github.com/dustin/go-jsonpointer/testdata/code.json.gz -------------------------------------------------------------------------------- /vendor/github.com/dustin/go-jsonpointer/testdata/pools.json: -------------------------------------------------------------------------------- 1 | { 2 | "componentsVersion": { 3 | "ale": "8cffe61", 4 | "couch": "1.2.0a-be4fa61-git", 5 | "couch_index_merger": "1.2.0a-be4fa61-git", 6 | "couch_set_view": "1.2.0a-be4fa61-git", 7 | "couch_view_parser": "1.0.0", 8 | "crypto": "2.0.4", 9 | "inets": "5.7.1", 10 | "kernel": "2.14.5", 11 | "lhttpc": "1.3.0", 12 | "mapreduce": "1.0.0", 13 | "mnesia": "4.5", 14 | "mochiweb": "1.4.1", 15 | "ns_server": "2.0.0-1976-rel-enterprise", 16 | "oauth": "7d85d3ef", 17 | "os_mon": "2.2.7", 18 | "public_key": "0.13", 19 | "sasl": "2.1.10", 20 | "ssl": "4.1.6", 21 | "stdlib": "1.17.5" 22 | }, 23 | "implementationVersion": "2.0.0-1976-rel-enterprise", 24 | "isAdminCreds": false, 25 | "pools": [ 26 | { 27 | "name": "default", 28 | "streamingUri": "/poolsStreaming/default?uuid=8820305b03a5d328c54bafde4c3f469e", 29 | "uri": "/pools/default?uuid=8820305b03a5d328c54bafde4c3f469e" 30 | } 31 | ], 32 | "settings": { 33 | "maxParallelIndexers": "/settings/maxParallelIndexers?uuid=8820305b03a5d328c54bafde4c3f469e", 34 | "viewUpdateDaemon": "/settings/viewUpdateDaemon?uuid=8820305b03a5d328c54bafde4c3f469e" 35 | }, 36 | "uuid": "8820305b03a5d328c54bafde4c3f469e" 37 | } 38 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/gojson/.gitignore: -------------------------------------------------------------------------------- 1 | #* 2 | *~ 3 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/gojson/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/gojson/bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Large data benchmark. 6 | // The JSON data is a summary of agl's changes in the 7 | // go, webkit, and chromium open source projects. 8 | // We benchmark converting between the JSON form 9 | // and in-memory data structures. 10 | 11 | package json 12 | 13 | import ( 14 | "bytes" 15 | "compress/gzip" 16 | "io/ioutil" 17 | "os" 18 | "testing" 19 | ) 20 | 21 | type codeResponse struct { 22 | Tree *codeNode `json:"tree"` 23 | Username string `json:"username"` 24 | } 25 | 26 | type codeNode struct { 27 | Name string `json:"name"` 28 | Kids []*codeNode `json:"kids"` 29 | CLWeight float64 `json:"cl_weight"` 30 | Touches int `json:"touches"` 31 | MinT int64 `json:"min_t"` 32 | MaxT int64 `json:"max_t"` 33 | MeanT int64 `json:"mean_t"` 34 | } 35 | 36 | var codeJSON []byte 37 | var codeStruct codeResponse 38 | 39 | func codeInit() { 40 | f, err := os.Open("testdata/code.json.gz") 41 | if err != nil { 42 | panic(err) 43 | } 44 | defer f.Close() 45 | gz, err := gzip.NewReader(f) 46 | if err != nil { 47 | panic(err) 48 | } 49 | data, err := ioutil.ReadAll(gz) 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | codeJSON = data 55 | 56 | if err := Unmarshal(codeJSON, &codeStruct); err != nil { 57 | panic("unmarshal code.json: " + err.Error()) 58 | } 59 | 60 | if data, err = Marshal(&codeStruct); err != nil { 61 | panic("marshal code.json: " + err.Error()) 62 | } 63 | 64 | if !bytes.Equal(data, codeJSON) { 65 | println("different lengths", len(data), len(codeJSON)) 66 | for i := 0; i < len(data) && i < len(codeJSON); i++ { 67 | if data[i] != codeJSON[i] { 68 | println("re-marshal: changed at byte", i) 69 | println("orig: ", string(codeJSON[i-10:i+10])) 70 | println("new: ", string(data[i-10:i+10])) 71 | break 72 | } 73 | } 74 | panic("re-marshal code.json: different result") 75 | } 76 | } 77 | 78 | func BenchmarkCodeEncoder(b *testing.B) { 79 | if codeJSON == nil { 80 | b.StopTimer() 81 | codeInit() 82 | b.StartTimer() 83 | } 84 | enc := NewEncoder(ioutil.Discard) 85 | for i := 0; i < b.N; i++ { 86 | if err := enc.Encode(&codeStruct); err != nil { 87 | b.Fatal("Encode:", err) 88 | } 89 | } 90 | b.SetBytes(int64(len(codeJSON))) 91 | } 92 | 93 | func BenchmarkCodeMarshal(b *testing.B) { 94 | if codeJSON == nil { 95 | b.StopTimer() 96 | codeInit() 97 | b.StartTimer() 98 | } 99 | for i := 0; i < b.N; i++ { 100 | if _, err := Marshal(&codeStruct); err != nil { 101 | b.Fatal("Marshal:", err) 102 | } 103 | } 104 | b.SetBytes(int64(len(codeJSON))) 105 | } 106 | 107 | func BenchmarkCodeDecoder(b *testing.B) { 108 | if codeJSON == nil { 109 | b.StopTimer() 110 | codeInit() 111 | b.StartTimer() 112 | } 113 | var buf bytes.Buffer 114 | dec := NewDecoder(&buf) 115 | var r codeResponse 116 | for i := 0; i < b.N; i++ { 117 | buf.Write(codeJSON) 118 | // hide EOF 119 | buf.WriteByte('\n') 120 | buf.WriteByte('\n') 121 | buf.WriteByte('\n') 122 | if err := dec.Decode(&r); err != nil { 123 | b.Fatal("Decode:", err) 124 | } 125 | } 126 | b.SetBytes(int64(len(codeJSON))) 127 | } 128 | 129 | func BenchmarkCodeUnmarshal(b *testing.B) { 130 | if codeJSON == nil { 131 | b.StopTimer() 132 | codeInit() 133 | b.StartTimer() 134 | } 135 | for i := 0; i < b.N; i++ { 136 | var r codeResponse 137 | if err := Unmarshal(codeJSON, &r); err != nil { 138 | b.Fatal("Unmmarshal:", err) 139 | } 140 | } 141 | b.SetBytes(int64(len(codeJSON))) 142 | } 143 | 144 | func BenchmarkCodeUnmarshalReuse(b *testing.B) { 145 | if codeJSON == nil { 146 | b.StopTimer() 147 | codeInit() 148 | b.StartTimer() 149 | } 150 | var r codeResponse 151 | for i := 0; i < b.N; i++ { 152 | if err := Unmarshal(codeJSON, &r); err != nil { 153 | b.Fatal("Unmmarshal:", err) 154 | } 155 | } 156 | } 157 | 158 | func BenchmarkUnmarshalString(b *testing.B) { 159 | data := []byte(`"hello, world"`) 160 | var s string 161 | 162 | for i := 0; i < b.N; i++ { 163 | if err := Unmarshal(data, &s); err != nil { 164 | b.Fatal("Unmarshal:", err) 165 | } 166 | } 167 | } 168 | 169 | func BenchmarkUnmarshalFloat64(b *testing.B) { 170 | var f float64 171 | data := []byte(`3.14`) 172 | 173 | for i := 0; i < b.N; i++ { 174 | if err := Unmarshal(data, &f); err != nil { 175 | b.Fatal("Unmarshal:", err) 176 | } 177 | } 178 | } 179 | 180 | func BenchmarkUnmarshalInt64(b *testing.B) { 181 | var x int64 182 | data := []byte(`3`) 183 | 184 | for i := 0; i < b.N; i++ { 185 | if err := Unmarshal(data, &x); err != nil { 186 | b.Fatal("Unmarshal:", err) 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/gojson/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package json_test 6 | 7 | import ( 8 | "bytes" 9 | "encoding/json" 10 | "fmt" 11 | "io" 12 | "log" 13 | "os" 14 | "strings" 15 | ) 16 | 17 | func ExampleMarshal() { 18 | type ColorGroup struct { 19 | ID int 20 | Name string 21 | Colors []string 22 | } 23 | group := ColorGroup{ 24 | ID: 1, 25 | Name: "Reds", 26 | Colors: []string{"Crimson", "Red", "Ruby", "Maroon"}, 27 | } 28 | b, err := json.Marshal(group) 29 | if err != nil { 30 | fmt.Println("error:", err) 31 | } 32 | os.Stdout.Write(b) 33 | // Output: 34 | // {"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]} 35 | } 36 | 37 | func ExampleUnmarshal() { 38 | var jsonBlob = []byte(`[ 39 | {"Name": "Platypus", "Order": "Monotremata"}, 40 | {"Name": "Quoll", "Order": "Dasyuromorphia"} 41 | ]`) 42 | type Animal struct { 43 | Name string 44 | Order string 45 | } 46 | var animals []Animal 47 | err := json.Unmarshal(jsonBlob, &animals) 48 | if err != nil { 49 | fmt.Println("error:", err) 50 | } 51 | fmt.Printf("%+v", animals) 52 | // Output: 53 | // [{Name:Platypus Order:Monotremata} {Name:Quoll Order:Dasyuromorphia}] 54 | } 55 | 56 | // This example uses a Decoder to decode a stream of distinct JSON values. 57 | func ExampleDecoder() { 58 | const jsonStream = ` 59 | {"Name": "Ed", "Text": "Knock knock."} 60 | {"Name": "Sam", "Text": "Who's there?"} 61 | {"Name": "Ed", "Text": "Go fmt."} 62 | {"Name": "Sam", "Text": "Go fmt who?"} 63 | {"Name": "Ed", "Text": "Go fmt yourself!"} 64 | ` 65 | type Message struct { 66 | Name, Text string 67 | } 68 | dec := json.NewDecoder(strings.NewReader(jsonStream)) 69 | for { 70 | var m Message 71 | if err := dec.Decode(&m); err == io.EOF { 72 | break 73 | } else if err != nil { 74 | log.Fatal(err) 75 | } 76 | fmt.Printf("%s: %s\n", m.Name, m.Text) 77 | } 78 | // Output: 79 | // Ed: Knock knock. 80 | // Sam: Who's there? 81 | // Ed: Go fmt. 82 | // Sam: Go fmt who? 83 | // Ed: Go fmt yourself! 84 | } 85 | 86 | // This example uses RawMessage to delay parsing part of a JSON message. 87 | func ExampleRawMessage() { 88 | type Color struct { 89 | Space string 90 | Point json.RawMessage // delay parsing until we know the color space 91 | } 92 | type RGB struct { 93 | R uint8 94 | G uint8 95 | B uint8 96 | } 97 | type YCbCr struct { 98 | Y uint8 99 | Cb int8 100 | Cr int8 101 | } 102 | 103 | var j = []byte(`[ 104 | {"Space": "YCbCr", "Point": {"Y": 255, "Cb": 0, "Cr": -10}}, 105 | {"Space": "RGB", "Point": {"R": 98, "G": 218, "B": 255}} 106 | ]`) 107 | var colors []Color 108 | err := json.Unmarshal(j, &colors) 109 | if err != nil { 110 | log.Fatalln("error:", err) 111 | } 112 | 113 | for _, c := range colors { 114 | var dst interface{} 115 | switch c.Space { 116 | case "RGB": 117 | dst = new(RGB) 118 | case "YCbCr": 119 | dst = new(YCbCr) 120 | } 121 | err := json.Unmarshal(c.Point, dst) 122 | if err != nil { 123 | log.Fatalln("error:", err) 124 | } 125 | fmt.Println(c.Space, dst) 126 | } 127 | // Output: 128 | // YCbCr &{255 0 -10} 129 | // RGB &{98 218 255} 130 | } 131 | 132 | func ExampleIndent() { 133 | type Road struct { 134 | Name string 135 | Number int 136 | } 137 | roads := []Road{ 138 | {"Diamond Fork", 29}, 139 | {"Sheep Creek", 51}, 140 | } 141 | 142 | b, err := json.Marshal(roads) 143 | if err != nil { 144 | log.Fatal(err) 145 | } 146 | 147 | var out bytes.Buffer 148 | json.Indent(&out, b, "=", "\t") 149 | out.WriteTo(os.Stdout) 150 | // Output: 151 | // [ 152 | // = { 153 | // = "Name": "Diamond Fork", 154 | // = "Number": 29 155 | // = }, 156 | // = { 157 | // = "Name": "Sheep Creek", 158 | // = "Number": 51 159 | // = } 160 | // =] 161 | } 162 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/gojson/fold.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package json 6 | 7 | import ( 8 | "bytes" 9 | "unicode/utf8" 10 | ) 11 | 12 | const ( 13 | caseMask = ^byte(0x20) // Mask to ignore case in ASCII. 14 | kelvin = '\u212a' 15 | smallLongEss = '\u017f' 16 | ) 17 | 18 | // foldFunc returns one of four different case folding equivalence 19 | // functions, from most general (and slow) to fastest: 20 | // 21 | // 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8 22 | // 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S') 23 | // 3) asciiEqualFold, no special, but includes non-letters (including _) 24 | // 4) simpleLetterEqualFold, no specials, no non-letters. 25 | // 26 | // The letters S and K are special because they map to 3 runes, not just 2: 27 | // * S maps to s and to U+017F 'ſ' Latin small letter long s 28 | // * k maps to K and to U+212A 'K' Kelvin sign 29 | // See http://play.golang.org/p/tTxjOc0OGo 30 | // 31 | // The returned function is specialized for matching against s and 32 | // should only be given s. It's not curried for performance reasons. 33 | func foldFunc(s []byte) func(s, t []byte) bool { 34 | nonLetter := false 35 | special := false // special letter 36 | for _, b := range s { 37 | if b >= utf8.RuneSelf { 38 | return bytes.EqualFold 39 | } 40 | upper := b & caseMask 41 | if upper < 'A' || upper > 'Z' { 42 | nonLetter = true 43 | } else if upper == 'K' || upper == 'S' { 44 | // See above for why these letters are special. 45 | special = true 46 | } 47 | } 48 | if special { 49 | return equalFoldRight 50 | } 51 | if nonLetter { 52 | return asciiEqualFold 53 | } 54 | return simpleLetterEqualFold 55 | } 56 | 57 | // equalFoldRight is a specialization of bytes.EqualFold when s is 58 | // known to be all ASCII (including punctuation), but contains an 's', 59 | // 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t. 60 | // See comments on foldFunc. 61 | func equalFoldRight(s, t []byte) bool { 62 | for _, sb := range s { 63 | if len(t) == 0 { 64 | return false 65 | } 66 | tb := t[0] 67 | if tb < utf8.RuneSelf { 68 | if sb != tb { 69 | sbUpper := sb & caseMask 70 | if 'A' <= sbUpper && sbUpper <= 'Z' { 71 | if sbUpper != tb&caseMask { 72 | return false 73 | } 74 | } else { 75 | return false 76 | } 77 | } 78 | t = t[1:] 79 | continue 80 | } 81 | // sb is ASCII and t is not. t must be either kelvin 82 | // sign or long s; sb must be s, S, k, or K. 83 | tr, size := utf8.DecodeRune(t) 84 | switch sb { 85 | case 's', 'S': 86 | if tr != smallLongEss { 87 | return false 88 | } 89 | case 'k', 'K': 90 | if tr != kelvin { 91 | return false 92 | } 93 | default: 94 | return false 95 | } 96 | t = t[size:] 97 | 98 | } 99 | if len(t) > 0 { 100 | return false 101 | } 102 | return true 103 | } 104 | 105 | // asciiEqualFold is a specialization of bytes.EqualFold for use when 106 | // s is all ASCII (but may contain non-letters) and contains no 107 | // special-folding letters. 108 | // See comments on foldFunc. 109 | func asciiEqualFold(s, t []byte) bool { 110 | if len(s) != len(t) { 111 | return false 112 | } 113 | for i, sb := range s { 114 | tb := t[i] 115 | if sb == tb { 116 | continue 117 | } 118 | if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') { 119 | if sb&caseMask != tb&caseMask { 120 | return false 121 | } 122 | } else { 123 | return false 124 | } 125 | } 126 | return true 127 | } 128 | 129 | // simpleLetterEqualFold is a specialization of bytes.EqualFold for 130 | // use when s is all ASCII letters (no underscores, etc) and also 131 | // doesn't contain 'k', 'K', 's', or 'S'. 132 | // See comments on foldFunc. 133 | func simpleLetterEqualFold(s, t []byte) bool { 134 | if len(s) != len(t) { 135 | return false 136 | } 137 | for i, b := range s { 138 | if b&caseMask != t[i]&caseMask { 139 | return false 140 | } 141 | } 142 | return true 143 | } 144 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/gojson/fold_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package json 6 | 7 | import ( 8 | "bytes" 9 | "strings" 10 | "testing" 11 | "unicode/utf8" 12 | ) 13 | 14 | var foldTests = []struct { 15 | fn func(s, t []byte) bool 16 | s, t string 17 | want bool 18 | }{ 19 | {equalFoldRight, "", "", true}, 20 | {equalFoldRight, "a", "a", true}, 21 | {equalFoldRight, "", "a", false}, 22 | {equalFoldRight, "a", "", false}, 23 | {equalFoldRight, "a", "A", true}, 24 | {equalFoldRight, "AB", "ab", true}, 25 | {equalFoldRight, "AB", "ac", false}, 26 | {equalFoldRight, "sbkKc", "ſbKKc", true}, 27 | {equalFoldRight, "SbKkc", "ſbKKc", true}, 28 | {equalFoldRight, "SbKkc", "ſbKK", false}, 29 | {equalFoldRight, "e", "é", false}, 30 | {equalFoldRight, "s", "S", true}, 31 | 32 | {simpleLetterEqualFold, "", "", true}, 33 | {simpleLetterEqualFold, "abc", "abc", true}, 34 | {simpleLetterEqualFold, "abc", "ABC", true}, 35 | {simpleLetterEqualFold, "abc", "ABCD", false}, 36 | {simpleLetterEqualFold, "abc", "xxx", false}, 37 | 38 | {asciiEqualFold, "a_B", "A_b", true}, 39 | {asciiEqualFold, "aa@", "aa`", false}, // verify 0x40 and 0x60 aren't case-equivalent 40 | } 41 | 42 | func TestFold(t *testing.T) { 43 | for i, tt := range foldTests { 44 | if got := tt.fn([]byte(tt.s), []byte(tt.t)); got != tt.want { 45 | t.Errorf("%d. %q, %q = %v; want %v", i, tt.s, tt.t, got, tt.want) 46 | } 47 | truth := strings.EqualFold(tt.s, tt.t) 48 | if truth != tt.want { 49 | t.Errorf("strings.EqualFold doesn't agree with case %d", i) 50 | } 51 | } 52 | } 53 | 54 | func TestFoldAgainstUnicode(t *testing.T) { 55 | const bufSize = 5 56 | buf1 := make([]byte, 0, bufSize) 57 | buf2 := make([]byte, 0, bufSize) 58 | var runes []rune 59 | for i := 0x20; i <= 0x7f; i++ { 60 | runes = append(runes, rune(i)) 61 | } 62 | runes = append(runes, kelvin, smallLongEss) 63 | 64 | funcs := []struct { 65 | name string 66 | fold func(s, t []byte) bool 67 | letter bool // must be ASCII letter 68 | simple bool // must be simple ASCII letter (not 'S' or 'K') 69 | }{ 70 | { 71 | name: "equalFoldRight", 72 | fold: equalFoldRight, 73 | }, 74 | { 75 | name: "asciiEqualFold", 76 | fold: asciiEqualFold, 77 | simple: true, 78 | }, 79 | { 80 | name: "simpleLetterEqualFold", 81 | fold: simpleLetterEqualFold, 82 | simple: true, 83 | letter: true, 84 | }, 85 | } 86 | 87 | for _, ff := range funcs { 88 | for _, r := range runes { 89 | if r >= utf8.RuneSelf { 90 | continue 91 | } 92 | if ff.letter && !isASCIILetter(byte(r)) { 93 | continue 94 | } 95 | if ff.simple && (r == 's' || r == 'S' || r == 'k' || r == 'K') { 96 | continue 97 | } 98 | for _, r2 := range runes { 99 | buf1 := append(buf1[:0], 'x') 100 | buf2 := append(buf2[:0], 'x') 101 | buf1 = buf1[:1+utf8.EncodeRune(buf1[1:bufSize], r)] 102 | buf2 = buf2[:1+utf8.EncodeRune(buf2[1:bufSize], r2)] 103 | buf1 = append(buf1, 'x') 104 | buf2 = append(buf2, 'x') 105 | want := bytes.EqualFold(buf1, buf2) 106 | if got := ff.fold(buf1, buf2); got != want { 107 | t.Errorf("%s(%q, %q) = %v; want %v", ff.name, buf1, buf2, got, want) 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | func isASCIILetter(b byte) bool { 115 | return ('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z') 116 | } 117 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/gojson/indent.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package json 6 | 7 | import "bytes" 8 | 9 | // Compact appends to dst the JSON-encoded src with 10 | // insignificant space characters elided. 11 | func Compact(dst *bytes.Buffer, src []byte) error { 12 | return compact(dst, src, false) 13 | } 14 | 15 | func compact(dst *bytes.Buffer, src []byte, escape bool) error { 16 | origLen := dst.Len() 17 | var scan Scanner 18 | scan.Reset() 19 | start := 0 20 | for i, c := range src { 21 | if escape && (c == '<' || c == '>' || c == '&') { 22 | if start < i { 23 | dst.Write(src[start:i]) 24 | } 25 | dst.WriteString(`\u00`) 26 | dst.WriteByte(hex[c>>4]) 27 | dst.WriteByte(hex[c&0xF]) 28 | start = i + 1 29 | } 30 | // Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9). 31 | if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 { 32 | if start < i { 33 | dst.Write(src[start:i]) 34 | } 35 | dst.WriteString(`\u202`) 36 | dst.WriteByte(hex[src[i+2]&0xF]) 37 | start = i + 3 38 | } 39 | v := scan.Step(&scan, int(c)) 40 | if v >= ScanSkipSpace { 41 | if v == ScanError { 42 | break 43 | } 44 | if start < i { 45 | dst.Write(src[start:i]) 46 | } 47 | start = i + 1 48 | } 49 | } 50 | if scan.EOF() == ScanError { 51 | dst.Truncate(origLen) 52 | return scan.Err 53 | } 54 | if start < len(src) { 55 | dst.Write(src[start:]) 56 | } 57 | return nil 58 | } 59 | 60 | func newline(dst *bytes.Buffer, prefix, indent string, depth int) { 61 | dst.WriteByte('\n') 62 | dst.WriteString(prefix) 63 | for i := 0; i < depth; i++ { 64 | dst.WriteString(indent) 65 | } 66 | } 67 | 68 | // Indent appends to dst an indented form of the JSON-encoded src. 69 | // Each element in a JSON object or array begins on a new, 70 | // indented line beginning with prefix followed by one or more 71 | // copies of indent according to the indentation nesting. 72 | // The data appended to dst does not begin with the prefix nor 73 | // any indentation, and has no trailing newline, to make it 74 | // easier to embed inside other formatted JSON data. 75 | func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error { 76 | origLen := dst.Len() 77 | var scan Scanner 78 | scan.Reset() 79 | needIndent := false 80 | depth := 0 81 | for _, c := range src { 82 | scan.bytes++ 83 | v := scan.Step(&scan, int(c)) 84 | if v == ScanSkipSpace { 85 | continue 86 | } 87 | if v == ScanError { 88 | break 89 | } 90 | if needIndent && v != ScanEndObject && v != ScanEndArray { 91 | needIndent = false 92 | depth++ 93 | newline(dst, prefix, indent, depth) 94 | } 95 | 96 | // Emit semantically uninteresting bytes 97 | // (in particular, punctuation in strings) unmodified. 98 | if v == ScanContinue { 99 | dst.WriteByte(c) 100 | continue 101 | } 102 | 103 | // Add spacing around real punctuation. 104 | switch c { 105 | case '{', '[': 106 | // delay indent so that empty object and array are formatted as {} and []. 107 | needIndent = true 108 | dst.WriteByte(c) 109 | 110 | case ',': 111 | dst.WriteByte(c) 112 | newline(dst, prefix, indent, depth) 113 | 114 | case ':': 115 | dst.WriteByte(c) 116 | dst.WriteByte(' ') 117 | 118 | case '}', ']': 119 | if needIndent { 120 | // suppress indent in empty object/array 121 | needIndent = false 122 | } else { 123 | depth-- 124 | newline(dst, prefix, indent, depth) 125 | } 126 | dst.WriteByte(c) 127 | 128 | default: 129 | dst.WriteByte(c) 130 | } 131 | } 132 | if scan.EOF() == ScanError { 133 | dst.Truncate(origLen) 134 | return scan.Err 135 | } 136 | return nil 137 | } 138 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/gojson/stream.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package json 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "io" 11 | ) 12 | 13 | // A Decoder reads and decodes JSON objects from an input stream. 14 | type Decoder struct { 15 | r io.Reader 16 | buf []byte 17 | d decodeState 18 | scan Scanner 19 | err error 20 | } 21 | 22 | // NewDecoder returns a new decoder that reads from r. 23 | // 24 | // The decoder introduces its own buffering and may 25 | // read data from r beyond the JSON values requested. 26 | func NewDecoder(r io.Reader) *Decoder { 27 | return &Decoder{r: r} 28 | } 29 | 30 | // UseNumber causes the Decoder to unmarshal a number into an interface{} as a 31 | // Number instead of as a float64. 32 | func (dec *Decoder) UseNumber() { dec.d.useNumber = true } 33 | 34 | // Decode reads the next JSON-encoded value from its 35 | // input and stores it in the value pointed to by v. 36 | // 37 | // See the documentation for Unmarshal for details about 38 | // the conversion of JSON into a Go value. 39 | func (dec *Decoder) Decode(v interface{}) error { 40 | if dec.err != nil { 41 | return dec.err 42 | } 43 | 44 | n, err := dec.readValue() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | // Don't save err from unmarshal into dec.err: 50 | // the connection is still usable since we read a complete JSON 51 | // object from it before the error happened. 52 | dec.d.init(dec.buf[0:n]) 53 | err = dec.d.unmarshal(v) 54 | 55 | // Slide rest of data down. 56 | rest := copy(dec.buf, dec.buf[n:]) 57 | dec.buf = dec.buf[0:rest] 58 | 59 | return err 60 | } 61 | 62 | // Buffered returns a reader of the data remaining in the Decoder's 63 | // buffer. The reader is valid until the next call to Decode. 64 | func (dec *Decoder) Buffered() io.Reader { 65 | return bytes.NewReader(dec.buf) 66 | } 67 | 68 | // readValue reads a JSON value into dec.buf. 69 | // It returns the length of the encoding. 70 | func (dec *Decoder) readValue() (int, error) { 71 | dec.scan.Reset() 72 | 73 | scanp := 0 74 | var err error 75 | Input: 76 | for { 77 | // Look in the buffer for a new value. 78 | for i, c := range dec.buf[scanp:] { 79 | dec.scan.bytes++ 80 | v := dec.scan.Step(&dec.scan, int(c)) 81 | if v == ScanEnd { 82 | scanp += i 83 | break Input 84 | } 85 | // scanEnd is delayed one byte. 86 | // We might block trying to get that byte from src, 87 | // so instead invent a space byte. 88 | if (v == ScanEndObject || v == ScanEndArray) && dec.scan.Step(&dec.scan, ' ') == ScanEnd { 89 | scanp += i + 1 90 | break Input 91 | } 92 | if v == ScanError { 93 | dec.err = dec.scan.Err 94 | return 0, dec.scan.Err 95 | } 96 | } 97 | scanp = len(dec.buf) 98 | 99 | // Did the last read have an error? 100 | // Delayed until now to allow buffer scan. 101 | if err != nil { 102 | if err == io.EOF { 103 | if dec.scan.Step(&dec.scan, ' ') == ScanEnd { 104 | break Input 105 | } 106 | if nonSpace(dec.buf) { 107 | err = io.ErrUnexpectedEOF 108 | } 109 | } 110 | dec.err = err 111 | return 0, err 112 | } 113 | 114 | // Make room to read more into the buffer. 115 | const minRead = 512 116 | if cap(dec.buf)-len(dec.buf) < minRead { 117 | newBuf := make([]byte, len(dec.buf), 2*cap(dec.buf)+minRead) 118 | copy(newBuf, dec.buf) 119 | dec.buf = newBuf 120 | } 121 | 122 | // Read. Delay error for next iteration (after scan). 123 | var n int 124 | n, err = dec.r.Read(dec.buf[len(dec.buf):cap(dec.buf)]) 125 | dec.buf = dec.buf[0 : len(dec.buf)+n] 126 | } 127 | return scanp, nil 128 | } 129 | 130 | func nonSpace(b []byte) bool { 131 | for _, c := range b { 132 | if !isSpace(rune(c)) { 133 | return true 134 | } 135 | } 136 | return false 137 | } 138 | 139 | // An Encoder writes JSON objects to an output stream. 140 | type Encoder struct { 141 | w io.Writer 142 | err error 143 | } 144 | 145 | // NewEncoder returns a new encoder that writes to w. 146 | func NewEncoder(w io.Writer) *Encoder { 147 | return &Encoder{w: w} 148 | } 149 | 150 | // Encode writes the JSON encoding of v to the stream, 151 | // followed by a newline character. 152 | // 153 | // See the documentation for Marshal for details about the 154 | // conversion of Go values to JSON. 155 | func (enc *Encoder) Encode(v interface{}) error { 156 | if enc.err != nil { 157 | return enc.err 158 | } 159 | e := newEncodeState() 160 | err := e.marshal(v) 161 | if err != nil { 162 | return err 163 | } 164 | 165 | // Terminate each value with a newline. 166 | // This makes the output look a little nicer 167 | // when debugging, and some kind of space 168 | // is required if the encoded value was a number, 169 | // so that the reader knows there aren't more 170 | // digits coming. 171 | e.WriteByte('\n') 172 | 173 | if _, err = enc.w.Write(e.Bytes()); err != nil { 174 | enc.err = err 175 | } 176 | encodeStatePool.Put(e) 177 | return err 178 | } 179 | 180 | // RawMessage is a raw encoded JSON object. 181 | // It implements Marshaler and Unmarshaler and can 182 | // be used to delay JSON decoding or precompute a JSON encoding. 183 | type RawMessage []byte 184 | 185 | // MarshalJSON returns *m as the JSON encoding of m. 186 | func (m *RawMessage) MarshalJSON() ([]byte, error) { 187 | return *m, nil 188 | } 189 | 190 | // UnmarshalJSON sets *m to a copy of data. 191 | func (m *RawMessage) UnmarshalJSON(data []byte) error { 192 | if m == nil { 193 | return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") 194 | } 195 | *m = append((*m)[0:0], data...) 196 | return nil 197 | } 198 | 199 | var _ Marshaler = (*RawMessage)(nil) 200 | var _ Unmarshaler = (*RawMessage)(nil) 201 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/gojson/stream_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package json 6 | 7 | import ( 8 | "bytes" 9 | "io/ioutil" 10 | "net" 11 | "reflect" 12 | "strings" 13 | "testing" 14 | ) 15 | 16 | // Test values for the stream test. 17 | // One of each JSON kind. 18 | var streamTest = []interface{}{ 19 | 0.1, 20 | "hello", 21 | nil, 22 | true, 23 | false, 24 | []interface{}{"a", "b", "c"}, 25 | map[string]interface{}{"K": "Kelvin", "ß": "long s"}, 26 | 3.14, // another value to make sure something can follow map 27 | } 28 | 29 | var streamEncoded = `0.1 30 | "hello" 31 | null 32 | true 33 | false 34 | ["a","b","c"] 35 | {"ß":"long s","K":"Kelvin"} 36 | 3.14 37 | ` 38 | 39 | func TestEncoder(t *testing.T) { 40 | for i := 0; i <= len(streamTest); i++ { 41 | var buf bytes.Buffer 42 | enc := NewEncoder(&buf) 43 | for j, v := range streamTest[0:i] { 44 | if err := enc.Encode(v); err != nil { 45 | t.Fatalf("encode #%d: %v", j, err) 46 | } 47 | } 48 | if have, want := buf.String(), nlines(streamEncoded, i); have != want { 49 | t.Errorf("encoding %d items: mismatch", i) 50 | diff(t, []byte(have), []byte(want)) 51 | break 52 | } 53 | } 54 | } 55 | 56 | func TestDecoder(t *testing.T) { 57 | for i := 0; i <= len(streamTest); i++ { 58 | // Use stream without newlines as input, 59 | // just to stress the decoder even more. 60 | // Our test input does not include back-to-back numbers. 61 | // Otherwise stripping the newlines would 62 | // merge two adjacent JSON values. 63 | var buf bytes.Buffer 64 | for _, c := range nlines(streamEncoded, i) { 65 | if c != '\n' { 66 | buf.WriteRune(c) 67 | } 68 | } 69 | out := make([]interface{}, i) 70 | dec := NewDecoder(&buf) 71 | for j := range out { 72 | if err := dec.Decode(&out[j]); err != nil { 73 | t.Fatalf("decode #%d/%d: %v", j, i, err) 74 | } 75 | } 76 | if !reflect.DeepEqual(out, streamTest[0:i]) { 77 | t.Errorf("decoding %d items: mismatch", i) 78 | for j := range out { 79 | if !reflect.DeepEqual(out[j], streamTest[j]) { 80 | t.Errorf("#%d: have %v want %v", j, out[j], streamTest[j]) 81 | } 82 | } 83 | break 84 | } 85 | } 86 | } 87 | 88 | func TestDecoderBuffered(t *testing.T) { 89 | r := strings.NewReader(`{"Name": "Gopher"} extra `) 90 | var m struct { 91 | Name string 92 | } 93 | d := NewDecoder(r) 94 | err := d.Decode(&m) 95 | if err != nil { 96 | t.Fatal(err) 97 | } 98 | if m.Name != "Gopher" { 99 | t.Errorf("Name = %q; want Gopher", m.Name) 100 | } 101 | rest, err := ioutil.ReadAll(d.Buffered()) 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | if g, w := string(rest), " extra "; g != w { 106 | t.Errorf("Remaining = %q; want %q", g, w) 107 | } 108 | } 109 | 110 | func nlines(s string, n int) string { 111 | if n <= 0 { 112 | return "" 113 | } 114 | for i, c := range s { 115 | if c == '\n' { 116 | if n--; n == 0 { 117 | return s[0 : i+1] 118 | } 119 | } 120 | } 121 | return s 122 | } 123 | 124 | func TestRawMessage(t *testing.T) { 125 | // TODO(rsc): Should not need the * in *RawMessage 126 | var data struct { 127 | X float64 128 | Id *RawMessage 129 | Y float32 130 | } 131 | const raw = `["\u0056",null]` 132 | const msg = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}` 133 | err := Unmarshal([]byte(msg), &data) 134 | if err != nil { 135 | t.Fatalf("Unmarshal: %v", err) 136 | } 137 | if string([]byte(*data.Id)) != raw { 138 | t.Fatalf("Raw mismatch: have %#q want %#q", []byte(*data.Id), raw) 139 | } 140 | b, err := Marshal(&data) 141 | if err != nil { 142 | t.Fatalf("Marshal: %v", err) 143 | } 144 | if string(b) != msg { 145 | t.Fatalf("Marshal: have %#q want %#q", b, msg) 146 | } 147 | } 148 | 149 | func TestNullRawMessage(t *testing.T) { 150 | // TODO(rsc): Should not need the * in *RawMessage 151 | var data struct { 152 | X float64 153 | Id *RawMessage 154 | Y float32 155 | } 156 | data.Id = new(RawMessage) 157 | const msg = `{"X":0.1,"Id":null,"Y":0.2}` 158 | err := Unmarshal([]byte(msg), &data) 159 | if err != nil { 160 | t.Fatalf("Unmarshal: %v", err) 161 | } 162 | if data.Id != nil { 163 | t.Fatalf("Raw mismatch: have non-nil, want nil") 164 | } 165 | b, err := Marshal(&data) 166 | if err != nil { 167 | t.Fatalf("Marshal: %v", err) 168 | } 169 | if string(b) != msg { 170 | t.Fatalf("Marshal: have %#q want %#q", b, msg) 171 | } 172 | } 173 | 174 | var blockingTests = []string{ 175 | `{"x": 1}`, 176 | `[1, 2, 3]`, 177 | } 178 | 179 | func TestBlocking(t *testing.T) { 180 | for _, enc := range blockingTests { 181 | r, w := net.Pipe() 182 | go w.Write([]byte(enc)) 183 | var val interface{} 184 | 185 | // If Decode reads beyond what w.Write writes above, 186 | // it will block, and the test will deadlock. 187 | if err := NewDecoder(r).Decode(&val); err != nil { 188 | t.Errorf("decoding %s: %v", enc, err) 189 | } 190 | r.Close() 191 | w.Close() 192 | } 193 | } 194 | 195 | func BenchmarkEncoderEncode(b *testing.B) { 196 | b.ReportAllocs() 197 | type T struct { 198 | X, Y string 199 | } 200 | v := &T{"foo", "bar"} 201 | for i := 0; i < b.N; i++ { 202 | if err := NewEncoder(ioutil.Discard).Encode(v); err != nil { 203 | b.Fatal(err) 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/gojson/tagkey_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package json 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | type basicLatin2xTag struct { 12 | V string `json:"$%-/"` 13 | } 14 | 15 | type basicLatin3xTag struct { 16 | V string `json:"0123456789"` 17 | } 18 | 19 | type basicLatin4xTag struct { 20 | V string `json:"ABCDEFGHIJKLMO"` 21 | } 22 | 23 | type basicLatin5xTag struct { 24 | V string `json:"PQRSTUVWXYZ_"` 25 | } 26 | 27 | type basicLatin6xTag struct { 28 | V string `json:"abcdefghijklmno"` 29 | } 30 | 31 | type basicLatin7xTag struct { 32 | V string `json:"pqrstuvwxyz"` 33 | } 34 | 35 | type miscPlaneTag struct { 36 | V string `json:"色は匂へど"` 37 | } 38 | 39 | type percentSlashTag struct { 40 | V string `json:"text/html%"` // http://golang.org/issue/2718 41 | } 42 | 43 | type punctuationTag struct { 44 | V string `json:"!#$%&()*+-./:<=>?@[]^_{|}~"` // http://golang.org/issue/3546 45 | } 46 | 47 | type emptyTag struct { 48 | W string 49 | } 50 | 51 | type misnamedTag struct { 52 | X string `jsom:"Misnamed"` 53 | } 54 | 55 | type badFormatTag struct { 56 | Y string `:"BadFormat"` 57 | } 58 | 59 | type badCodeTag struct { 60 | Z string `json:" !\"#&'()*+,."` 61 | } 62 | 63 | type spaceTag struct { 64 | Q string `json:"With space"` 65 | } 66 | 67 | type unicodeTag struct { 68 | W string `json:"Ελλάδα"` 69 | } 70 | 71 | var structTagObjectKeyTests = []struct { 72 | raw interface{} 73 | value string 74 | key string 75 | }{ 76 | {basicLatin2xTag{"2x"}, "2x", "$%-/"}, 77 | {basicLatin3xTag{"3x"}, "3x", "0123456789"}, 78 | {basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"}, 79 | {basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"}, 80 | {basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"}, 81 | {basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"}, 82 | {miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"}, 83 | {emptyTag{"Pour Moi"}, "Pour Moi", "W"}, 84 | {misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"}, 85 | {badFormatTag{"Orfevre"}, "Orfevre", "Y"}, 86 | {badCodeTag{"Reliable Man"}, "Reliable Man", "Z"}, 87 | {percentSlashTag{"brut"}, "brut", "text/html%"}, 88 | {punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:<=>?@[]^_{|}~"}, 89 | {spaceTag{"Perreddu"}, "Perreddu", "With space"}, 90 | {unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"}, 91 | } 92 | 93 | func TestStructTagObjectKey(t *testing.T) { 94 | for _, tt := range structTagObjectKeyTests { 95 | b, err := Marshal(tt.raw) 96 | if err != nil { 97 | t.Fatalf("Marshal(%#q) failed: %v", tt.raw, err) 98 | } 99 | var f interface{} 100 | err = Unmarshal(b, &f) 101 | if err != nil { 102 | t.Fatalf("Unmarshal(%#q) failed: %v", b, err) 103 | } 104 | for i, v := range f.(map[string]interface{}) { 105 | switch i { 106 | case tt.key: 107 | if s, ok := v.(string); !ok || s != tt.value { 108 | t.Fatalf("Unexpected value: %#q, want %v", s, tt.value) 109 | } 110 | default: 111 | t.Fatalf("Unexpected key: %#q, from %#q", i, b) 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/gojson/tags.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package json 6 | 7 | import ( 8 | "strings" 9 | ) 10 | 11 | // tagOptions is the string following a comma in a struct field's "json" 12 | // tag, or the empty string. It does not include the leading comma. 13 | type tagOptions string 14 | 15 | // parseTag splits a struct field's json tag into its name and 16 | // comma-separated options. 17 | func parseTag(tag string) (string, tagOptions) { 18 | if idx := strings.Index(tag, ","); idx != -1 { 19 | return tag[:idx], tagOptions(tag[idx+1:]) 20 | } 21 | return tag, tagOptions("") 22 | } 23 | 24 | // Contains reports whether a comma-separated list of options 25 | // contains a particular substr flag. substr must be surrounded by a 26 | // string boundary or commas. 27 | func (o tagOptions) Contains(optionName string) bool { 28 | if len(o) == 0 { 29 | return false 30 | } 31 | s := string(o) 32 | for s != "" { 33 | var next string 34 | i := strings.Index(s, ",") 35 | if i >= 0 { 36 | s, next = s[:i], s[i+1:] 37 | } 38 | if s == optionName { 39 | return true 40 | } 41 | s = next 42 | } 43 | return false 44 | } 45 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/gojson/tags_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package json 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func TestTagParsing(t *testing.T) { 12 | name, opts := parseTag("field,foobar,foo") 13 | if name != "field" { 14 | t.Fatalf("name = %q, want field", name) 15 | } 16 | for _, tt := range []struct { 17 | opt string 18 | want bool 19 | }{ 20 | {"foobar", true}, 21 | {"foo", true}, 22 | {"bar", false}, 23 | } { 24 | if opts.Contains(tt.opt) != tt.want { 25 | t.Errorf("Contains(%q) = %v", tt.opt, !tt.want) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /vendor/github.com/dustin/gojson/testdata/code.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/anaconda/fba449f7b405a61af4f8a9246bd557f9625bd7be/vendor/github.com/dustin/gojson/testdata/code.json.gz -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/.gitignore: -------------------------------------------------------------------------------- 1 | _testmain.go 2 | *.6 3 | *.a 4 | 6.out 5 | example 6 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | sudo: false 4 | 5 | go: 6 | - 1.6 7 | - 1.7 8 | - 1.8 9 | - tip 10 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/README.markdown: -------------------------------------------------------------------------------- 1 | # Go-OAuth 2 | 3 | [![GoDoc](https://godoc.org/github.com/garyburd/go-oauth/oauth?status.svg)](https://godoc.org/github.com/garyburd/go-oauth/oauth) 4 | [![Build Status](https://travis-ci.org/garyburd/go-oauth.svg?branch=master)](https://travis-ci.org/garyburd/go-oauth) 5 | 6 | Go-OAuth is a [Go](http://golang.org/) client for the OAuth 1.0, OAuth 1.0a and 7 | [RFC 5849](https://tools.ietf.org/html/rfc5849) Protocols. The package supports 8 | HMAC-SHA1, RSA-SHA1 and PLAINTEXT signatures. 9 | 10 | ## Installation 11 | 12 | go get github.com/garyburd/go-oauth/oauth 13 | 14 | ## License 15 | 16 | Go-OAuth is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). 17 | 18 | ## Documentation 19 | 20 | - [Reference](http://godoc.org/github.com/garyburd/go-oauth/oauth) 21 | - Examples 22 | - [Discogs](http://github.com/garyburd/go-oauth/tree/master/examples/discogs) 23 | - [Dropbox](http://github.com/garyburd/go-oauth/tree/master/examples/dropbox) 24 | - [Quickbooks](http://github.com/garyburd/go-oauth/tree/master/examples/quickbooks) 25 | - [SmugMug](https://github.com/garyburd/go-oauth/tree/master/examples/smugmug) 26 | - [Twitter on App Engine](http://github.com/garyburd/go-oauth/tree/master/examples/appengine) 27 | - [Twitter](http://github.com/garyburd/go-oauth/tree/master/examples/twitter) 28 | - [Twitter OOB](http://github.com/garyburd/go-oauth/tree/master/examples/twitteroob) (a command line application using OOB authorization) 29 | - [Yelp](https://github.com/garyburd/go-oauth/tree/master/examples/yelp) 30 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/appengine/README.markdown: -------------------------------------------------------------------------------- 1 | This example shows how to use the oauth package on App Engine. 2 | 3 | The examples require a configuration file containing a consumer key and secret from Twitter: 4 | 5 | 1. Register an application at https://dev.twitter.com/apps/new 6 | 2. $ cp config.json.example config.json. 7 | 3. Edit config.json to include your Twitter consumer key and consumer secret from step 1. 8 | 9 | To run the web example: 10 | 11 | 1. devapp\_server.py . 12 | 2. Go to http://127.0.0.1:8080/ in a browser to try the application. 13 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/appengine/app.yaml: -------------------------------------------------------------------------------- 1 | application: helloworld 2 | version: 1 3 | runtime: go 4 | api_version: go1 5 | 6 | handlers: 7 | - url: /favicon\.ico 8 | static_files: favicon.ico 9 | upload: favicon\.ico 10 | 11 | - url: /.* 12 | script: _go_app 13 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/appengine/config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "Token":"", 3 | "Secret":"" 4 | } 5 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/discogs/README.markdown: -------------------------------------------------------------------------------- 1 | This example shows how to use the oauth package with [Discogs](http://www.discogs.com/developers/). 2 | 3 | The examples require a configuration file containing a consumer key and secret: 4 | 5 | 1. [Create an application](https://www.discogs.com/settings/developers). 6 | 2. $ cp config.json.example config.json. 7 | 3. Edit config.json to include your consumer key and secret from step 1. 8 | 9 | 10 | To run the web example: 11 | 12 | 1. $ go run main.go 13 | 2. Go to http://127.0.0.1:8080/ in a browser to try the application. 14 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/discogs/config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "Token":"", 3 | "Secret":"" 4 | } 5 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/discogs/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/json" 19 | "flag" 20 | "fmt" 21 | "io/ioutil" 22 | "log" 23 | "net/http" 24 | "net/url" 25 | "text/template" 26 | 27 | "github.com/garyburd/go-oauth/examples/session" 28 | "github.com/garyburd/go-oauth/oauth" 29 | ) 30 | 31 | // Session state keys. 32 | const ( 33 | tempCredKey = "tempCred" 34 | tokenCredKey = "tokenCred" 35 | ) 36 | 37 | var oauthClient = oauth.Client{ 38 | TemporaryCredentialRequestURI: "https://api.discogs.com/oauth/request_token", 39 | ResourceOwnerAuthorizationURI: "https://www.discogs.com/oauth/authorize", 40 | TokenRequestURI: "https://api.discogs.com/oauth/access_token", 41 | Header: http.Header{"User-Agent": {"ExampleDiscogsClient/1.0"}}, 42 | } 43 | 44 | var credPath = flag.String("config", "config.json", "Path to configuration file containing the application's credentials.") 45 | 46 | func readCredentials() error { 47 | b, err := ioutil.ReadFile(*credPath) 48 | if err != nil { 49 | return err 50 | } 51 | return json.Unmarshal(b, &oauthClient.Credentials) 52 | } 53 | 54 | // serveLogin gets the OAuth temp credentials and redirects the user to the 55 | // Discogs' authorization page. 56 | func serveLogin(w http.ResponseWriter, r *http.Request) { 57 | callback := "http://" + r.Host + "/callback" 58 | tempCred, err := oauthClient.RequestTemporaryCredentials(nil, callback, nil) 59 | if err != nil { 60 | http.Error(w, "Error getting temp cred, "+err.Error(), 500) 61 | return 62 | } 63 | s := session.Get(r) 64 | s[tempCredKey] = tempCred 65 | if err := session.Save(w, r, s); err != nil { 66 | http.Error(w, "Error saving session , "+err.Error(), 500) 67 | return 68 | } 69 | http.Redirect(w, r, oauthClient.AuthorizationURL(tempCred, nil), 302) 70 | } 71 | 72 | // serveOAuthCallback handles callbacks from the OAuth server. 73 | func serveOAuthCallback(w http.ResponseWriter, r *http.Request) { 74 | s := session.Get(r) 75 | tempCred, _ := s[tempCredKey].(*oauth.Credentials) 76 | if tempCred == nil || tempCred.Token != r.FormValue("oauth_token") { 77 | http.Error(w, "Unknown oauth_token.", 500) 78 | return 79 | } 80 | tokenCred, _, err := oauthClient.RequestToken(nil, tempCred, r.FormValue("oauth_verifier")) 81 | if err != nil { 82 | http.Error(w, "Error getting request token, "+err.Error(), 500) 83 | return 84 | } 85 | delete(s, tempCredKey) 86 | s[tokenCredKey] = tokenCred 87 | if err := session.Save(w, r, s); err != nil { 88 | http.Error(w, "Error saving session , "+err.Error(), 500) 89 | return 90 | } 91 | http.Redirect(w, r, "/", 302) 92 | } 93 | 94 | // serveLogout clears the token credentials 95 | func serveLogout(w http.ResponseWriter, r *http.Request) { 96 | s := session.Get(r) 97 | delete(s, tokenCredKey) 98 | if err := session.Save(w, r, s); err != nil { 99 | http.Error(w, "Error saving session , "+err.Error(), 500) 100 | return 101 | } 102 | http.Redirect(w, r, "/", 302) 103 | } 104 | 105 | // authHandler reads the auth cookie and invokes a handler with the result. 106 | type authHandler struct { 107 | handler func(w http.ResponseWriter, r *http.Request, c *oauth.Credentials) 108 | optional bool 109 | } 110 | 111 | func (h *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 112 | cred, _ := session.Get(r)[tokenCredKey].(*oauth.Credentials) 113 | if cred == nil && !h.optional { 114 | http.Error(w, "Not logged in.", 403) 115 | return 116 | } 117 | h.handler(w, r, cred) 118 | } 119 | 120 | // getJSON gets a resource from the Discogs API server and decodes the result as JSON. 121 | func getJSON(cred *oauth.Credentials, endpoint string, form url.Values, v interface{}) error { 122 | resp, err := oauthClient.Get(nil, cred, "https://api.discogs.com"+endpoint, form) 123 | if err != nil { 124 | return err 125 | } 126 | defer resp.Body.Close() 127 | if resp.StatusCode != 200 { 128 | return fmt.Errorf("server returned status %d", resp.StatusCode) 129 | } 130 | return json.NewDecoder(resp.Body).Decode(v) 131 | } 132 | 133 | // respond responds to a request by executing the html template t with data. 134 | func respond(w http.ResponseWriter, t *template.Template, data interface{}) { 135 | w.Header().Set("Content-Type", "text/html; charset=utf-8") 136 | if err := t.Execute(w, data); err != nil { 137 | log.Print(err) 138 | } 139 | } 140 | 141 | func serveHome(w http.ResponseWriter, r *http.Request, cred *oauth.Credentials) { 142 | if r.URL.Path != "/" { 143 | http.NotFound(w, r) 144 | return 145 | } 146 | if cred == nil { 147 | data := struct{ Host string }{r.Host} 148 | respond(w, homeLoggedOutTmpl, &data) 149 | } else { 150 | var data struct { 151 | Username string 152 | } 153 | if err := getJSON(cred, "/oauth/identity", nil, &data); err != nil { 154 | http.Error(w, err.Error(), 500) 155 | return 156 | } 157 | respond(w, homeTmpl, &data) 158 | } 159 | } 160 | 161 | var httpAddr = flag.String("addr", ":8080", "HTTP server address") 162 | 163 | func main() { 164 | flag.Parse() 165 | if err := readCredentials(); err != nil { 166 | log.Fatalf("Error reading configuration, %v", err) 167 | } 168 | 169 | http.Handle("/", &authHandler{handler: serveHome, optional: true}) 170 | http.HandleFunc("/login", serveLogin) 171 | http.HandleFunc("/logout", serveLogout) 172 | http.HandleFunc("/callback", serveOAuthCallback) 173 | if err := http.ListenAndServe(*httpAddr, nil); err != nil { 174 | log.Fatalf("Error listening, %v", err) 175 | } 176 | } 177 | 178 | var ( 179 | homeLoggedOutTmpl = template.Must(template.New("loggedout").Parse( 180 | ` 181 | 182 | login 183 | 184 | `)) 185 | 186 | homeTmpl = template.Must(template.New("home").Parse( 187 | ` 188 | 189 |

Welcome {{.Username}}! 190 | `)) 191 | ) 192 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/dropbox/README.markdown: -------------------------------------------------------------------------------- 1 | This example shows how to use the oauth package with Dropbox. 2 | 3 | To run this example: 4 | 5 | 1. Register an application at https://www.dropbox.com/developers/apps 6 | 2. $ cp config.json.example config.json. 7 | 3. Edit config.json to include your Dropbox application key and application secret from step 1. 8 | 4. $ go run main.go 9 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/dropbox/config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "Token":"", 3 | "Secret":"" 4 | } 5 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/dropbox/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/json" 19 | "flag" 20 | "fmt" 21 | "io/ioutil" 22 | "log" 23 | "net/http" 24 | "net/url" 25 | "text/template" 26 | 27 | "github.com/garyburd/go-oauth/examples/session" 28 | "github.com/garyburd/go-oauth/oauth" 29 | ) 30 | 31 | // Session state keys. 32 | var ( 33 | tempCredKey = "tempCred" 34 | tokenCredKey = "tokenCred" 35 | ) 36 | 37 | var oauthClient = oauth.Client{ 38 | TemporaryCredentialRequestURI: "https://api.dropbox.com/1/oauth/request_token", 39 | ResourceOwnerAuthorizationURI: "https://www.dropbox.com/1/oauth/authorize", 40 | TokenRequestURI: "https://api.dropbox.com/1/oauth/access_token", 41 | SignatureMethod: oauth.PLAINTEXT, // Dropbox also works with HMACSHA1 42 | } 43 | 44 | var credPath = flag.String("config", "config.json", "Path to configuration file containing the application's credentials.") 45 | 46 | func readCredentials() error { 47 | b, err := ioutil.ReadFile(*credPath) 48 | if err != nil { 49 | return err 50 | } 51 | return json.Unmarshal(b, &oauthClient.Credentials) 52 | } 53 | 54 | // serveLogin gets the OAuth temp credentials and redirects the user to the 55 | // OAuth server's authorization page. 56 | func serveLogin(w http.ResponseWriter, r *http.Request) { 57 | // Dropbox supports the older OAuth 1.0 specification where the callback URL 58 | // is passed to the authorization endpoint. 59 | callback := "http://" + r.Host + "/callback" 60 | tempCred, err := oauthClient.RequestTemporaryCredentials(nil, "", nil) 61 | if err != nil { 62 | http.Error(w, "Error getting temp cred, "+err.Error(), 500) 63 | return 64 | } 65 | s := session.Get(r) 66 | s[tempCredKey] = tempCred 67 | if err := session.Save(w, r, s); err != nil { 68 | http.Error(w, "Error saving session , "+err.Error(), 500) 69 | return 70 | } 71 | http.Redirect(w, r, oauthClient.AuthorizationURL(tempCred, url.Values{"oauth_callback": {callback}}), 302) 72 | } 73 | 74 | // serveOAuthCallback handles callbacks from the OAuth server. 75 | func serveOAuthCallback(w http.ResponseWriter, r *http.Request) { 76 | s := session.Get(r) 77 | tempCred, _ := s[tempCredKey].(*oauth.Credentials) 78 | if tempCred == nil || tempCred.Token != r.FormValue("oauth_token") { 79 | http.Error(w, "Unknown oauth_token.", 500) 80 | return 81 | } 82 | tokenCred, _, err := oauthClient.RequestToken(nil, tempCred, r.FormValue("oauth_verifier")) 83 | if err != nil { 84 | http.Error(w, "Error getting request token, "+err.Error(), 500) 85 | return 86 | } 87 | delete(s, tempCredKey) 88 | s[tokenCredKey] = tokenCred 89 | if err := session.Save(w, r, s); err != nil { 90 | http.Error(w, "Error saving session , "+err.Error(), 500) 91 | return 92 | } 93 | http.Redirect(w, r, "/", 302) 94 | } 95 | 96 | // serveLogout clears the authentication cookie. 97 | func serveLogout(w http.ResponseWriter, r *http.Request) { 98 | s := session.Get(r) 99 | delete(s, tokenCredKey) 100 | if err := session.Save(w, r, s); err != nil { 101 | http.Error(w, "Error saving session , "+err.Error(), 500) 102 | return 103 | } 104 | http.Redirect(w, r, "/", 302) 105 | } 106 | 107 | // authHandler reads the auth cookie and invokes a handler with the result. 108 | type authHandler struct { 109 | handler func(w http.ResponseWriter, r *http.Request, c *oauth.Credentials) 110 | optional bool 111 | } 112 | 113 | func (h *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 114 | cred, _ := session.Get(r)[tokenCredKey].(*oauth.Credentials) 115 | if cred == nil && !h.optional { 116 | http.Error(w, "Not logged in.", 403) 117 | return 118 | } 119 | h.handler(w, r, cred) 120 | } 121 | 122 | // respond responds to a request by executing the html template t with data. 123 | func respond(w http.ResponseWriter, t *template.Template, data interface{}) { 124 | w.Header().Set("Content-Type", "text/html; charset=utf-8") 125 | if err := t.Execute(w, data); err != nil { 126 | log.Print(err) 127 | } 128 | } 129 | 130 | func serveHome(w http.ResponseWriter, r *http.Request, cred *oauth.Credentials) { 131 | if r.URL.Path != "/" { 132 | http.NotFound(w, r) 133 | return 134 | } 135 | if cred == nil { 136 | respond(w, homeLoggedOutTmpl, nil) 137 | } else { 138 | respond(w, homeTmpl, nil) 139 | } 140 | } 141 | 142 | func serveInfo(w http.ResponseWriter, r *http.Request, cred *oauth.Credentials) { 143 | resp, err := oauthClient.Get(nil, cred, "https://api.dropbox.com/1/account/info", nil) 144 | if err != nil { 145 | http.Error(w, "Error getting info: "+err.Error(), 500) 146 | return 147 | } 148 | defer resp.Body.Close() 149 | b, err := ioutil.ReadAll(resp.Body) 150 | if err != nil { 151 | http.Error(w, "Error reading body:"+err.Error(), 500) 152 | return 153 | } 154 | if resp.StatusCode != 200 { 155 | http.Error(w, fmt.Sprintf("Get account/info returned status %d, %s", resp.StatusCode, b), 500) 156 | return 157 | } 158 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") 159 | w.Write(b) 160 | } 161 | 162 | var httpAddr = flag.String("addr", ":8080", "HTTP server address") 163 | 164 | func main() { 165 | flag.Parse() 166 | if err := readCredentials(); err != nil { 167 | log.Fatalf("Error reading configuration, %v", err) 168 | } 169 | http.Handle("/", &authHandler{handler: serveHome, optional: true}) 170 | http.Handle("/info", &authHandler{handler: serveInfo}) 171 | http.HandleFunc("/login", serveLogin) 172 | http.HandleFunc("/logout", serveLogout) 173 | http.HandleFunc("/callback", serveOAuthCallback) 174 | if err := http.ListenAndServe(*httpAddr, nil); err != nil { 175 | log.Fatalf("Error listening, %v", err) 176 | } 177 | } 178 | 179 | var ( 180 | homeLoggedOutTmpl = template.Must(template.New("loggedout").Parse( 181 | ` 182 | 183 | 184 | 185 | login 186 | 187 | `)) 188 | 189 | homeTmpl = template.Must(template.New("home").Parse( 190 | ` 191 | 192 | 193 | 194 |

info 195 |

logout 196 | 197 | `)) 198 | ) 199 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/quickbooks/README.markdown: -------------------------------------------------------------------------------- 1 | This example shows how to use the oauth package with QuickBooks . 2 | 3 | The examples require a configuration file containing a consumer key and secret: 4 | 5 | 1. Apply to the developer program at https://developer.intuit.com/ and create an application. 6 | 2. $ cp config.json.example config.json. 7 | 3. Edit config.json to include your consumer key and secret from step 1. 8 | 9 | 10 | To run the web example: 11 | 12 | 1. $ go run main.go 13 | 2. Go to http://127.0.0.1:8080/ in a browser to try the application. 14 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/quickbooks/config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "Token":"", 3 | "Secret":"" 4 | } 5 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/session/session.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | // Package session implements a session store for the Go-OAuth examples. A 16 | // real application should not use this package. 17 | package session 18 | 19 | import ( 20 | "crypto/rand" 21 | "encoding/hex" 22 | "net/http" 23 | "sync" 24 | ) 25 | 26 | var ( 27 | mu sync.Mutex 28 | sessions = make(map[string]map[string]interface{}) 29 | ) 30 | 31 | // Get returns the session data for the request client. 32 | func Get(r *http.Request) (s map[string]interface{}) { 33 | if c, _ := r.Cookie("session"); c != nil && c.Value != "" { 34 | mu.Lock() 35 | s = sessions[c.Value] 36 | mu.Unlock() 37 | } 38 | if s == nil { 39 | s = make(map[string]interface{}) 40 | } 41 | return s 42 | } 43 | 44 | // Save saves session for the request client. 45 | func Save(w http.ResponseWriter, r *http.Request, s map[string]interface{}) error { 46 | key := "" 47 | if c, _ := r.Cookie("session"); c != nil { 48 | key = c.Value 49 | } 50 | if len(s) == 0 { 51 | if key != "" { 52 | mu.Lock() 53 | delete(sessions, key) 54 | mu.Unlock() 55 | } 56 | return nil 57 | } 58 | if key == "" { 59 | var buf [16]byte 60 | _, err := rand.Read(buf[:]) 61 | if err != nil { 62 | return err 63 | } 64 | key = hex.EncodeToString(buf[:]) 65 | http.SetCookie(w, &http.Cookie{ 66 | Name: "session", 67 | Path: "/", 68 | HttpOnly: true, 69 | Value: key, 70 | }) 71 | } 72 | mu.Lock() 73 | sessions[key] = s 74 | mu.Unlock() 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/smugmug/README.markdown: -------------------------------------------------------------------------------- 1 | This example shows how to use the oauth package with SmugMug. 2 | 3 | The examples require a configuration file containing a consumer key and secret: 4 | 5 | 1. Apply for an API key at http://www.smugmug.com/hack/apikeys 6 | 2. $ cp config.json.example config.json. 7 | 3. Edit config.json to include your Smugmug client key and secret from step 1. 8 | 9 | 10 | To run the web example: 11 | 12 | 1. $ go run main.go 13 | 2. Go to http://127.0.0.1:8080/ in a browser to try the application. 14 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/smugmug/config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "Token":"", 3 | "Secret":"" 4 | } 5 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/twitter/README.markdown: -------------------------------------------------------------------------------- 1 | This example shows how to use the oauth package with Twitter from a web application. 2 | 3 | The examples require a configuration file containing a consumer key and secret: 4 | 5 | 1. Register an application at https://dev.twitter.com/apps/new (Note: create a callback url for the app) 6 | 2. $ cp config.json.example config.json. 7 | 3. Edit config.json to include your Twitter consumer key and consumer secret from step 1. 8 | 9 | To run the example: 10 | 11 | 1. $ go run main.go 12 | 2. Go to http://127.0.0.1:8080/ in a browser to try the application. 13 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/twitter/config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "Token":"", 3 | "Secret":"" 4 | } 5 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/twitteroob/README.markdown: -------------------------------------------------------------------------------- 1 | This example shows how to use the oauth package with Twitter from a command line application. 2 | 3 | The examples require a configuration file containing a consumer key and secret: 4 | 5 | 1. Register an application at https://dev.twitter.com/apps/new (Note: create a callback url for the app) 6 | 2. $ cp config.json.example config.json. 7 | 3. Edit config.json to include your Twitter consumer key and consumer secret from step 1. 8 | 9 | To run the command line example with OOB authorization: 10 | 11 | 1. $ go run main.go 12 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/twitteroob/config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "Token":"", 3 | "Secret":"" 4 | } 5 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/twitteroob/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/json" 19 | "flag" 20 | "fmt" 21 | "io" 22 | "io/ioutil" 23 | "log" 24 | "os" 25 | 26 | "github.com/garyburd/go-oauth/oauth" 27 | ) 28 | 29 | var oauthClient = oauth.Client{ 30 | TemporaryCredentialRequestURI: "https://api.twitter.com/oauth/request_token", 31 | ResourceOwnerAuthorizationURI: "https://api.twitter.com/oauth/authorize", 32 | TokenRequestURI: "https://api.twitter.com/oauth/access_token", 33 | } 34 | 35 | var credPath = flag.String("config", "config.json", "Path to configuration file containing the application's credentials.") 36 | 37 | func readCredentials() error { 38 | b, err := ioutil.ReadFile(*credPath) 39 | if err != nil { 40 | return err 41 | } 42 | return json.Unmarshal(b, &oauthClient.Credentials) 43 | } 44 | 45 | func main() { 46 | if err := readCredentials(); err != nil { 47 | log.Fatal(err) 48 | } 49 | 50 | tempCred, err := oauthClient.RequestTemporaryCredentials(nil, "oob", nil) 51 | if err != nil { 52 | log.Fatal("RequestTemporaryCredentials:", err) 53 | } 54 | 55 | u := oauthClient.AuthorizationURL(tempCred, nil) 56 | 57 | fmt.Printf("1. Go to %s\n2. Authorize the application\n3. Enter verification code:\n", u) 58 | 59 | var code string 60 | fmt.Scanln(&code) 61 | 62 | tokenCred, _, err := oauthClient.RequestToken(nil, tempCred, code) 63 | if err != nil { 64 | log.Fatal(err) 65 | } 66 | 67 | resp, err := oauthClient.Get(nil, tokenCred, 68 | "https://api.twitter.com/1.1/statuses/home_timeline.json", nil) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | defer resp.Body.Close() 73 | if _, err := io.Copy(os.Stdout, resp.Body); err != nil { 74 | log.Fatal(err) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/yelp/README.markdown: -------------------------------------------------------------------------------- 1 | This example shows how to use the oauth package with Yelp. 2 | 3 | How to run the example: 4 | 5 | 1. Sign up and for a developer account and get your credentials at [the Yelp developer site](http://www.yelp.com/developers/manage_api_keys). 6 | 2. $ cp config.json.example config.json. 7 | 3. Edit config.json to include the credentials from step 1. 8 | 4. $ go run main.go 9 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/yelp/config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "ConsumerKey": "", 3 | "ConsumerSecret": "", 4 | "Token": "", 5 | "TokenSecret": "" 6 | } 7 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/examples/yelp/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/json" 19 | "flag" 20 | "fmt" 21 | "io/ioutil" 22 | "log" 23 | "net/url" 24 | 25 | "github.com/garyburd/go-oauth/oauth" 26 | ) 27 | 28 | type client struct { 29 | client oauth.Client 30 | token oauth.Credentials 31 | } 32 | 33 | func (c *client) get(urlStr string, params url.Values, v interface{}) error { 34 | resp, err := c.client.Get(nil, &c.token, urlStr, params) 35 | if err != nil { 36 | return err 37 | } 38 | defer resp.Body.Close() 39 | if resp.StatusCode != 200 { 40 | return fmt.Errorf("yelp status %d", resp.StatusCode) 41 | } 42 | return json.NewDecoder(resp.Body).Decode(v) 43 | } 44 | 45 | var credPath = flag.String("config", "config.json", "Path to configuration file containing the application's credentials.") 46 | 47 | func readCredentials(c *client) error { 48 | b, err := ioutil.ReadFile(*credPath) 49 | if err != nil { 50 | return err 51 | } 52 | var creds struct { 53 | ConsumerKey string 54 | ConsumerSecret string 55 | Token string 56 | TokenSecret string 57 | } 58 | if err := json.Unmarshal(b, &creds); err != nil { 59 | return err 60 | } 61 | c.client.Credentials.Token = creds.ConsumerKey 62 | c.client.Credentials.Secret = creds.ConsumerSecret 63 | c.token.Token = creds.Token 64 | c.token.Secret = creds.TokenSecret 65 | return nil 66 | } 67 | 68 | func main() { 69 | var c client 70 | if err := readCredentials(&c); err != nil { 71 | log.Fatal(err) 72 | } 73 | 74 | var data struct { 75 | Businesses []struct { 76 | Name string 77 | Location struct { 78 | DisplayAddress []string `json:"display_address"` 79 | } 80 | } 81 | } 82 | form := url.Values{"term": {"food"}, "location": {"San Francisco"}} 83 | if err := c.get("http://api.yelp.com/v2/search", form, &data); err != nil { 84 | log.Fatal(err) 85 | } 86 | 87 | for _, b := range data.Businesses { 88 | addr := "" 89 | if len(b.Location.DisplayAddress) > 0 { 90 | addr = b.Location.DisplayAddress[0] 91 | } 92 | log.Println(b.Name, addr) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/go-oauth/oauth/examples_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package oauth_test 16 | 17 | import ( 18 | "github.com/garyburd/go-oauth/oauth" 19 | "net/http" 20 | "net/url" 21 | "strings" 22 | ) 23 | 24 | // This example shows how to sign a request when the URL Opaque field is used. 25 | // See the note at http://golang.org/pkg/net/url/#URL for information on the 26 | // use of the URL Opaque field. 27 | func ExampleClient_SetAuthorizationHeader(client *oauth.Client, credentials *oauth.Credentials) error { 28 | form := url.Values{"maxResults": {"100"}} 29 | 30 | // The last element of path contains a "/". 31 | path := "/document/encoding%2gizp" 32 | 33 | // Create the request with the temporary path "/". 34 | req, err := http.NewRequest("GET", "http://api.example.com/", strings.NewReader(form.Encode())) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | // Overwrite the temporary path with the actual request path. 40 | req.URL.Opaque = path 41 | 42 | // Sign the request. 43 | if err := client.SetAuthorizationHeader(req.Header, credentials, "GET", req.URL, form); err != nil { 44 | return err 45 | } 46 | 47 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 48 | 49 | resp, err := http.DefaultClient.Do(req) 50 | if err != nil { 51 | return err 52 | } 53 | defer resp.Body.Close() 54 | // process the response 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /webhook.go: -------------------------------------------------------------------------------- 1 | package anaconda 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | //GetActivityWebhooks represents the twitter account_activity webhook 8 | //Returns all URLs and their statuses for the given app. Currently, 9 | //only one webhook URL can be registered to an application. 10 | //https://dev.twitter.com/webhooks/reference/get/account_activity/webhooks 11 | func (a TwitterApi) GetActivityWebhooks(v url.Values) (u []WebHookResp, err error) { 12 | v = cleanValues(v) 13 | responseCh := make(chan response) 14 | a.queryQueue <- query{a.baseUrl + "/account_activity/webhooks.json", v, &u, _GET, responseCh} 15 | return u, (<-responseCh).err 16 | } 17 | 18 | //WebHookResp represents the Get webhook responses 19 | type WebHookResp struct { 20 | ID string 21 | URL string 22 | Valid bool 23 | CreatedAt string 24 | } 25 | 26 | //SetActivityWebhooks represents to set twitter account_activity webhook 27 | //Registers a new webhook URL for the given application context. 28 | //The URL will be validated via CRC request before saving. In case the validation fails, 29 | //a comprehensive error is returned. message to the requester. 30 | //Only one webhook URL can be registered to an application. 31 | //https://api.twitter.com/1.1/account_activity/webhooks.json 32 | func (a TwitterApi) SetActivityWebhooks(v url.Values) (u WebHookResp, err error) { 33 | v = cleanValues(v) 34 | responseCh := make(chan response) 35 | a.queryQueue <- query{a.baseUrl + "/account_activity/webhooks.json", v, &u, _POST, responseCh} 36 | return u, (<-responseCh).err 37 | } 38 | 39 | //DeleteActivityWebhooks Removes the webhook from the provided application’s configuration. 40 | //https://dev.twitter.com/webhooks/reference/del/account_activity/webhooks 41 | func (a TwitterApi) DeleteActivityWebhooks(v url.Values, webhookID string) (u interface{}, err error) { 42 | v = cleanValues(v) 43 | responseCh := make(chan response) 44 | a.queryQueue <- query{a.baseUrl + "/account_activity/webhooks/" + webhookID + ".json", v, &u, _DELETE, responseCh} 45 | return u, (<-responseCh).err 46 | } 47 | 48 | //PutActivityWebhooks update webhook which reenables the webhook by setting its status to valid. 49 | //https://dev.twitter.com/webhooks/reference/put/account_activity/webhooks 50 | func (a TwitterApi) PutActivityWebhooks(v url.Values, webhookID string) (u interface{}, err error) { 51 | v = cleanValues(v) 52 | responseCh := make(chan response) 53 | a.queryQueue <- query{a.baseUrl + "/account_activity/webhooks/" + webhookID + ".json", v, &u, _PUT, responseCh} 54 | return u, (<-responseCh).err 55 | } 56 | 57 | //SetWHSubscription Subscribes the provided app to events for the provided user context. 58 | //When subscribed, all DM events for the provided user will be sent to the app’s webhook via POST request. 59 | //https://dev.twitter.com/webhooks/reference/post/account_activity/webhooks/subscriptions 60 | func (a TwitterApi) SetWHSubscription(v url.Values, webhookID string) (u interface{}, err error) { 61 | v = cleanValues(v) 62 | responseCh := make(chan response) 63 | a.queryQueue <- query{a.baseUrl + "/account_activity/webhooks/" + webhookID + "/subscriptions.json", v, &u, _POST, responseCh} 64 | return u, (<-responseCh).err 65 | } 66 | 67 | //GetWHSubscription Provides a way to determine if a webhook configuration is 68 | //subscribed to the provided user’s Direct Messages. 69 | //https://dev.twitter.com/webhooks/reference/get/account_activity/webhooks/subscriptions 70 | func (a TwitterApi) GetWHSubscription(v url.Values, webhookID string) (u interface{}, err error) { 71 | v = cleanValues(v) 72 | responseCh := make(chan response) 73 | a.queryQueue <- query{a.baseUrl + "/account_activity/webhooks/" + webhookID + "/subscriptions.json", v, &u, _GET, responseCh} 74 | return u, (<-responseCh).err 75 | } 76 | 77 | //DeleteWHSubscription Deactivates subscription for the provided user context and app. After deactivation, 78 | //all DM events for the requesting user will no longer be sent to the webhook URL.. 79 | //https://dev.twitter.com/webhooks/reference/del/account_activity/webhooks 80 | func (a TwitterApi) DeleteWHSubscription(v url.Values, webhookID string) (u interface{}, err error) { 81 | v = cleanValues(v) 82 | responseCh := make(chan response) 83 | a.queryQueue <- query{a.baseUrl + "/account_activity/webhooks/" + webhookID + "/subscriptions.json", v, &u, _DELETE, responseCh} 84 | return u, (<-responseCh).err 85 | } 86 | --------------------------------------------------------------------------------