mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
add functions/vendor files
This commit is contained in:
2
vendor/github.com/dghubble/go-twitter/.gitignore
generated
vendored
Normal file
2
vendor/github.com/dghubble/go-twitter/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/coverage
|
||||
/bin
|
||||
11
vendor/github.com/dghubble/go-twitter/.travis.yml
generated
vendored
Normal file
11
vendor/github.com/dghubble/go-twitter/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
||||
- tip
|
||||
install:
|
||||
- go get github.com/golang/lint/golint
|
||||
- go get -v -t ./twitter
|
||||
script:
|
||||
- ./test
|
||||
21
vendor/github.com/dghubble/go-twitter/LICENSE
generated
vendored
Normal file
21
vendor/github.com/dghubble/go-twitter/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Dalton Hubble
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
270
vendor/github.com/dghubble/go-twitter/README.md
generated
vendored
Normal file
270
vendor/github.com/dghubble/go-twitter/README.md
generated
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
|
||||
|
||||
# go-twitter [](https://travis-ci.org/dghubble/go-twitter) [](https://godoc.org/github.com/dghubble/go-twitter)
|
||||
<img align="right" src="https://storage.googleapis.com/dghubble/gopher-on-bird.png">
|
||||
|
||||
go-twitter is a Go client library for the [Twitter API](https://dev.twitter.com/rest/public). Check the [usage](#usage) section or try the [examples](/examples) to see how to access the Twitter API.
|
||||
|
||||
### Features
|
||||
|
||||
* Twitter REST API:
|
||||
* Accounts
|
||||
* Direct Messages
|
||||
* Favorites
|
||||
* Friends
|
||||
* Friendships
|
||||
* Followers
|
||||
* Search
|
||||
* Statuses
|
||||
* Timelines
|
||||
* Users
|
||||
* Twitter Streaming API
|
||||
* Public Streams
|
||||
* User Streams
|
||||
* Site Streams
|
||||
* Firehose Streams
|
||||
|
||||
## Install
|
||||
|
||||
go get github.com/dghubble/go-twitter/twitter
|
||||
|
||||
## Documentation
|
||||
|
||||
Read [GoDoc](https://godoc.org/github.com/dghubble/go-twitter/twitter)
|
||||
|
||||
## Usage
|
||||
|
||||
### REST API
|
||||
|
||||
The `twitter` package provides a `Client` for accessing the Twitter API. Here are some example requests.
|
||||
|
||||
```go
|
||||
config := oauth1.NewConfig("consumerKey", "consumerSecret")
|
||||
token := oauth1.NewToken("accessToken", "accessSecret")
|
||||
httpClient := config.Client(oauth1.NoContext, token)
|
||||
|
||||
// Twitter client
|
||||
client := twitter.NewClient(httpClient)
|
||||
|
||||
// Home Timeline
|
||||
tweets, resp, err := client.Timelines.HomeTimeline(&twitter.HomeTimelineParams{
|
||||
Count: 20,
|
||||
})
|
||||
|
||||
// Send a Tweet
|
||||
tweet, resp, err := client.Statuses.Update("just setting up my twttr", nil)
|
||||
|
||||
// Status Show
|
||||
tweet, resp, err := client.Statuses.Show(585613041028431872, nil)
|
||||
|
||||
// Search Tweets
|
||||
search, resp, err := client.Search.Tweets(&twitter.SearchTweetParams{
|
||||
Query: "gopher",
|
||||
})
|
||||
|
||||
// User Show
|
||||
user, resp, err := client.Users.Show(&twitter.UserShowParams{
|
||||
ScreenName: "dghubble",
|
||||
})
|
||||
|
||||
// Followers
|
||||
followers, resp, err := client.Followers.List(&twitter.FollowerListParams{})
|
||||
```
|
||||
|
||||
Authentication is handled by the `http.Client` passed to `NewClient` to handle user auth (OAuth1) or application auth (OAuth2). See the [Authentication](#authentication) section.
|
||||
|
||||
Required parameters are passed as positional arguments. Optional parameters are passed typed params structs (or nil).
|
||||
|
||||
## Streaming API
|
||||
|
||||
The Twitter Public, User, Site, and Firehose Streaming APIs can be accessed through the `Client` `StreamService` which provides methods `Filter`, `Sample`, `User`, `Site`, and `Firehose`.
|
||||
|
||||
Create a `Client` with an authenticated `http.Client`. All stream endpoints require a user auth context so choose an OAuth1 `http.Client`.
|
||||
|
||||
client := twitter.NewClient(httpClient)
|
||||
|
||||
Next, request a managed `Stream` be started.
|
||||
|
||||
#### Filter
|
||||
|
||||
Filter Streams return Tweets that match one or more filtering predicates such as `Track`, `Follow`, and `Locations`.
|
||||
|
||||
```go
|
||||
params := &twitter.StreamFilterParams{
|
||||
Track: []string{"kitten"},
|
||||
StallWarnings: twitter.Bool(true),
|
||||
}
|
||||
stream, err := client.Streams.Filter(params)
|
||||
```
|
||||
|
||||
#### User
|
||||
|
||||
User Streams provide messages specific to the authenticate User and possibly those they follow.
|
||||
|
||||
```go
|
||||
params := &twitter.StreamUserParams{
|
||||
With: "followings",
|
||||
StallWarnings: twitter.Bool(true),
|
||||
}
|
||||
stream, err := client.Streams.User(params)
|
||||
```
|
||||
|
||||
*Note* To see Direct Message events, your consumer application must ask Users for read/write/DM access to their account.
|
||||
|
||||
#### Sample
|
||||
|
||||
Sample Streams return a small sample of public Tweets.
|
||||
|
||||
```go
|
||||
params := &twitter.StreamSampleParams{
|
||||
StallWarnings: twitter.Bool(true),
|
||||
}
|
||||
stream, err := client.Streams.Sample(params)
|
||||
```
|
||||
|
||||
#### Site, Firehose
|
||||
|
||||
Site and Firehose Streams require your application to have special permissions, but their API works the same way.
|
||||
|
||||
### Receiving Messages
|
||||
|
||||
Each `Stream` maintains the connection to the Twitter Streaming API endpoint, receives messages, and sends them on the `Stream.Messages` channel.
|
||||
|
||||
Go channels support range iterations which allow you to read the messages which are of type `interface{}`.
|
||||
|
||||
```go
|
||||
for message := range stream.Messages {
|
||||
fmt.Println(message)
|
||||
}
|
||||
```
|
||||
|
||||
If you run this in your main goroutine, it will receive messages forever unless the stream stops. To continue execution, receive messages using a separate goroutine.
|
||||
|
||||
### Demux
|
||||
|
||||
Receiving messages of type `interface{}` isn't very nice, it means you'll have to type switch and probably filter out message types you don't care about.
|
||||
|
||||
For this, try a `Demux`, like `SwitchDemux`, which receives messages and type switches them to call functions with typed messages.
|
||||
|
||||
For example, say you're only interested in Tweets and Direct Messages.
|
||||
|
||||
```go
|
||||
demux := twitter.NewSwitchDemux()
|
||||
demux.Tweet = func(tweet *twitter.Tweet) {
|
||||
fmt.Println(tweet.Text)
|
||||
}
|
||||
demux.DM = func(dm *twitter.DirectMessage) {
|
||||
fmt.Println(dm.SenderID)
|
||||
}
|
||||
```
|
||||
|
||||
Pass the `Demux` each message or give it the entire `Stream.Message` channel.
|
||||
|
||||
```go
|
||||
for message := range stream.Messages {
|
||||
demux.Handle(message)
|
||||
}
|
||||
// or pass the channel
|
||||
demux.HandleChan(stream.Messages)
|
||||
```
|
||||
|
||||
### Stopping
|
||||
|
||||
The `Stream` will stop itself if the stream disconnects and retrying produces unrecoverable errors. When this occurs, `Stream` will close the `stream.Messages` channel, so execution will break out of any message *for range* loops.
|
||||
|
||||
When you are finished receiving from a `Stream`, call `Stop()` which closes the connection, channels, and stops the goroutine **before** returning. This ensures resources are properly cleaned up.
|
||||
|
||||
### Pitfalls
|
||||
|
||||
**Bad**: In this example, `Stop()` is unlikely to be reached. Control stays in the message loop unless the `Stream` becomes disconnected and cannot retry.
|
||||
|
||||
```go
|
||||
// program does not terminate :(
|
||||
stream, _ := client.Streams.Sample(params)
|
||||
for message := range stream.Messages {
|
||||
demux.Handle(message)
|
||||
}
|
||||
stream.Stop()
|
||||
```
|
||||
|
||||
**Bad**: Here, messages are received on a non-main goroutine, but then `Stop()` is called immediately. The `Stream` is stopped and the program exits.
|
||||
|
||||
```go
|
||||
// got no messages :(
|
||||
stream, _ := client.Streams.Sample(params)
|
||||
go demux.HandleChan(stream.Messages)
|
||||
stream.Stop()
|
||||
```
|
||||
|
||||
**Good**: For main package scripts, one option is to receive messages in a goroutine and wait for CTRL-C to be pressed, then explicitly stop the `Stream`.
|
||||
|
||||
```go
|
||||
stream, err := client.Streams.Sample(params)
|
||||
go demux.HandleChan(stream.Messages)
|
||||
|
||||
// Wait for SIGINT and SIGTERM (HIT CTRL-C)
|
||||
ch := make(chan os.Signal)
|
||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||
log.Println(<-ch)
|
||||
|
||||
stream.Stop()
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
The API client accepts an any `http.Client` capable of making user auth (OAuth1) or application auth (OAuth2) authorized requests. See the [dghubble/oauth1](https://github.com/dghubble/oauth1) and [golang/oauth2](https://github.com/golang/oauth2/) packages which can provide such agnostic clients.
|
||||
|
||||
Passing an `http.Client` directly grants you control over the underlying transport, avoids dependencies on particular OAuth1 or OAuth2 packages, and keeps client APIs separate from authentication protocols.
|
||||
|
||||
See the [google/go-github](https://github.com/google/go-github) client which takes the same approach.
|
||||
|
||||
For example, make requests as a consumer application on behalf of a user who has granted access, with OAuth1.
|
||||
|
||||
```go
|
||||
// OAuth1
|
||||
import (
|
||||
"github.com/dghubble/go-twitter/twitter"
|
||||
"github.com/dghubble/oauth1"
|
||||
)
|
||||
|
||||
config := oauth1.NewConfig("consumerKey", "consumerSecret")
|
||||
token := oauth1.NewToken("accessToken", "accessSecret")
|
||||
// http.Client will automatically authorize Requests
|
||||
httpClient := config.Client(oauth1.NoContext, token)
|
||||
|
||||
// Twitter client
|
||||
client := twitter.NewClient(httpClient)
|
||||
```
|
||||
|
||||
If no user auth context is needed, make requests as your application with application auth.
|
||||
|
||||
```go
|
||||
// OAuth2
|
||||
import (
|
||||
"github.com/dghubble/go-twitter/twitter"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
config := &oauth2.Config{}
|
||||
token := &oauth2.Token{AccessToken: accessToken}
|
||||
// http.Client will automatically authorize Requests
|
||||
httpClient := config.Client(oauth2.NoContext, token)
|
||||
|
||||
// Twitter client
|
||||
client := twitter.NewClient(httpClient)
|
||||
```
|
||||
|
||||
To implement Login with Twitter for web or mobile, see the gologin [package](https://github.com/dghubble/gologin) and [examples](https://github.com/dghubble/gologin/tree/master/examples/twitter).
|
||||
|
||||
## Roadmap
|
||||
|
||||
* Support gzipped streams
|
||||
* Auto-stop streams in the event of long stalls
|
||||
|
||||
## Contributing
|
||||
|
||||
See the [Contributing Guide](https://gist.github.com/dghubble/be682c123727f70bcfe7).
|
||||
|
||||
## License
|
||||
|
||||
[MIT License](LICENSE)
|
||||
42
vendor/github.com/dghubble/go-twitter/examples/README.md
generated
vendored
Normal file
42
vendor/github.com/dghubble/go-twitter/examples/README.md
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
# Examples
|
||||
|
||||
Get the dependencies and examples
|
||||
|
||||
cd examples
|
||||
go get .
|
||||
|
||||
## User Auth (OAuth1)
|
||||
|
||||
A user access token (OAuth1) grants a consumer application access to a user's Twitter resources.
|
||||
|
||||
Setup an OAuth1 `http.Client` with the consumer key and secret and oauth token and secret.
|
||||
|
||||
export TWITTER_CONSUMER_KEY=xxx
|
||||
export TWITTER_CONSUMER_SECRET=xxx
|
||||
export TWITTER_ACCESS_TOKEN=xxx
|
||||
export TWITTER_ACCESS_SECRET=xxx
|
||||
|
||||
To make requests as an application, on behalf of a user, create a `twitter` `Client` to get the home timeline, mention timeline, and more (example will **not** post Tweets).
|
||||
|
||||
go run user-auth.go
|
||||
|
||||
## App Auth (OAuth2)
|
||||
|
||||
An application access token (OAuth2) allows an application to make Twitter API requests for public content, with rate limits counting against the app itself. App auth requests can be made to API endpoints which do not require a user context.
|
||||
|
||||
Setup an OAuth2 `http.Client` with the Twitter application access token.
|
||||
|
||||
export TWITTER_APP_ACCESS_TOKEN=xxx
|
||||
|
||||
To make requests as an application, create a `twitter` `Client` and get public Tweets or timelines or other public content.
|
||||
|
||||
go run app-auth.go
|
||||
|
||||
## Streaming API
|
||||
|
||||
A user access token (OAuth1) is required for Streaming API requests. See above.
|
||||
|
||||
go run streaming.go
|
||||
|
||||
Hit CTRL-C to stop streaming. Uncomment different examples in code to try different streams.
|
||||
71
vendor/github.com/dghubble/go-twitter/examples/app-auth.go
generated
vendored
Normal file
71
vendor/github.com/dghubble/go-twitter/examples/app-auth.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/coreos/pkg/flagutil"
|
||||
"github.com/dghubble/go-twitter/twitter"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flags := flag.NewFlagSet("app-auth", flag.ExitOnError)
|
||||
accessToken := flags.String("app-access-token", "", "Twitter Application Access Token")
|
||||
flags.Parse(os.Args[1:])
|
||||
flagutil.SetFlagsFromEnv(flags, "TWITTER")
|
||||
|
||||
if *accessToken == "" {
|
||||
log.Fatal("Application Access Token required")
|
||||
}
|
||||
|
||||
config := &oauth2.Config{}
|
||||
token := &oauth2.Token{AccessToken: *accessToken}
|
||||
// OAuth2 http.Client will automatically authorize Requests
|
||||
httpClient := config.Client(oauth2.NoContext, token)
|
||||
|
||||
// Twitter client
|
||||
client := twitter.NewClient(httpClient)
|
||||
|
||||
// user show
|
||||
userShowParams := &twitter.UserShowParams{ScreenName: "golang"}
|
||||
user, _, _ := client.Users.Show(userShowParams)
|
||||
fmt.Printf("USERS SHOW:\n%+v\n", user)
|
||||
|
||||
// users lookup
|
||||
userLookupParams := &twitter.UserLookupParams{ScreenName: []string{"golang", "gophercon"}}
|
||||
users, _, _ := client.Users.Lookup(userLookupParams)
|
||||
fmt.Printf("USERS LOOKUP:\n%+v\n", users)
|
||||
|
||||
// status show
|
||||
statusShowParams := &twitter.StatusShowParams{}
|
||||
tweet, _, _ := client.Statuses.Show(584077528026849280, statusShowParams)
|
||||
fmt.Printf("STATUSES SHOW:\n%+v\n", tweet)
|
||||
|
||||
// statuses lookup
|
||||
statusLookupParams := &twitter.StatusLookupParams{ID: []int64{20}, TweetMode: "extended"}
|
||||
tweets, _, _ := client.Statuses.Lookup([]int64{573893817000140800}, statusLookupParams)
|
||||
fmt.Printf("STATUSES LOOKUP:\n%+v\n", tweets)
|
||||
|
||||
// oEmbed status
|
||||
statusOembedParams := &twitter.StatusOEmbedParams{ID: 691076766878691329, MaxWidth: 500}
|
||||
oembed, _, _ := client.Statuses.OEmbed(statusOembedParams)
|
||||
fmt.Printf("OEMBED TWEET:\n%+v\n", oembed)
|
||||
|
||||
// user timeline
|
||||
userTimelineParams := &twitter.UserTimelineParams{ScreenName: "golang", Count: 2}
|
||||
tweets, _, _ = client.Timelines.UserTimeline(userTimelineParams)
|
||||
fmt.Printf("USER TIMELINE:\n%+v\n", tweets)
|
||||
|
||||
// search tweets
|
||||
searchTweetParams := &twitter.SearchTweetParams{
|
||||
Query: "happy birthday",
|
||||
TweetMode: "extended",
|
||||
Count: 3,
|
||||
}
|
||||
search, _, _ := client.Search.Tweets(searchTweetParams)
|
||||
fmt.Printf("SEARCH TWEETS:\n%+v\n", search)
|
||||
fmt.Printf("SEARCH METADATA:\n%+v\n", search.Metadata)
|
||||
}
|
||||
91
vendor/github.com/dghubble/go-twitter/examples/streaming.go
generated
vendored
Normal file
91
vendor/github.com/dghubble/go-twitter/examples/streaming.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/coreos/pkg/flagutil"
|
||||
"github.com/dghubble/go-twitter/twitter"
|
||||
"github.com/dghubble/oauth1"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flags := flag.NewFlagSet("user-auth", flag.ExitOnError)
|
||||
consumerKey := flags.String("consumer-key", "", "Twitter Consumer Key")
|
||||
consumerSecret := flags.String("consumer-secret", "", "Twitter Consumer Secret")
|
||||
accessToken := flags.String("access-token", "", "Twitter Access Token")
|
||||
accessSecret := flags.String("access-secret", "", "Twitter Access Secret")
|
||||
flags.Parse(os.Args[1:])
|
||||
flagutil.SetFlagsFromEnv(flags, "TWITTER")
|
||||
|
||||
if *consumerKey == "" || *consumerSecret == "" || *accessToken == "" || *accessSecret == "" {
|
||||
log.Fatal("Consumer key/secret and Access token/secret required")
|
||||
}
|
||||
|
||||
config := oauth1.NewConfig(*consumerKey, *consumerSecret)
|
||||
token := oauth1.NewToken(*accessToken, *accessSecret)
|
||||
// OAuth1 http.Client will automatically authorize Requests
|
||||
httpClient := config.Client(oauth1.NoContext, token)
|
||||
|
||||
// Twitter Client
|
||||
client := twitter.NewClient(httpClient)
|
||||
|
||||
// Convenience Demux demultiplexed stream messages
|
||||
demux := twitter.NewSwitchDemux()
|
||||
demux.Tweet = func(tweet *twitter.Tweet) {
|
||||
fmt.Println(tweet.Text)
|
||||
}
|
||||
demux.DM = func(dm *twitter.DirectMessage) {
|
||||
fmt.Println(dm.SenderID)
|
||||
}
|
||||
demux.Event = func(event *twitter.Event) {
|
||||
fmt.Printf("%#v\n", event)
|
||||
}
|
||||
|
||||
fmt.Println("Starting Stream...")
|
||||
|
||||
// FILTER
|
||||
filterParams := &twitter.StreamFilterParams{
|
||||
Track: []string{"cat"},
|
||||
StallWarnings: twitter.Bool(true),
|
||||
}
|
||||
stream, err := client.Streams.Filter(filterParams)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// USER (quick test: auth'd user likes a tweet -> event)
|
||||
// userParams := &twitter.StreamUserParams{
|
||||
// StallWarnings: twitter.Bool(true),
|
||||
// With: "followings",
|
||||
// Language: []string{"en"},
|
||||
// }
|
||||
// stream, err := client.Streams.User(userParams)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
|
||||
// SAMPLE
|
||||
// sampleParams := &twitter.StreamSampleParams{
|
||||
// StallWarnings: twitter.Bool(true),
|
||||
// }
|
||||
// stream, err := client.Streams.Sample(sampleParams)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
|
||||
// Receive messages until stopped or stream quits
|
||||
go demux.HandleChan(stream.Messages)
|
||||
|
||||
// Wait for SIGINT and SIGTERM (HIT CTRL-C)
|
||||
ch := make(chan os.Signal)
|
||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||
log.Println(<-ch)
|
||||
|
||||
fmt.Println("Stopping Stream...")
|
||||
stream.Stop()
|
||||
}
|
||||
70
vendor/github.com/dghubble/go-twitter/examples/user-auth.go
generated
vendored
Normal file
70
vendor/github.com/dghubble/go-twitter/examples/user-auth.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/coreos/pkg/flagutil"
|
||||
"github.com/dghubble/go-twitter/twitter"
|
||||
"github.com/dghubble/oauth1"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flags := flag.NewFlagSet("user-auth", flag.ExitOnError)
|
||||
consumerKey := flags.String("consumer-key", "", "Twitter Consumer Key")
|
||||
consumerSecret := flags.String("consumer-secret", "", "Twitter Consumer Secret")
|
||||
accessToken := flags.String("access-token", "", "Twitter Access Token")
|
||||
accessSecret := flags.String("access-secret", "", "Twitter Access Secret")
|
||||
flags.Parse(os.Args[1:])
|
||||
flagutil.SetFlagsFromEnv(flags, "TWITTER")
|
||||
|
||||
if *consumerKey == "" || *consumerSecret == "" || *accessToken == "" || *accessSecret == "" {
|
||||
log.Fatal("Consumer key/secret and Access token/secret required")
|
||||
}
|
||||
|
||||
config := oauth1.NewConfig(*consumerKey, *consumerSecret)
|
||||
token := oauth1.NewToken(*accessToken, *accessSecret)
|
||||
// OAuth1 http.Client will automatically authorize Requests
|
||||
httpClient := config.Client(oauth1.NoContext, token)
|
||||
|
||||
// Twitter client
|
||||
client := twitter.NewClient(httpClient)
|
||||
|
||||
// Verify Credentials
|
||||
verifyParams := &twitter.AccountVerifyParams{
|
||||
SkipStatus: twitter.Bool(true),
|
||||
IncludeEmail: twitter.Bool(true),
|
||||
}
|
||||
user, _, _ := client.Accounts.VerifyCredentials(verifyParams)
|
||||
fmt.Printf("User's ACCOUNT:\n%+v\n", user)
|
||||
|
||||
// Home Timeline
|
||||
homeTimelineParams := &twitter.HomeTimelineParams{
|
||||
Count: 2,
|
||||
TweetMode: "extended",
|
||||
}
|
||||
tweets, _, _ := client.Timelines.HomeTimeline(homeTimelineParams)
|
||||
fmt.Printf("User's HOME TIMELINE:\n%+v\n", tweets)
|
||||
|
||||
// Mention Timeline
|
||||
mentionTimelineParams := &twitter.MentionTimelineParams{
|
||||
Count: 2,
|
||||
TweetMode: "extended",
|
||||
}
|
||||
tweets, _, _ = client.Timelines.MentionTimeline(mentionTimelineParams)
|
||||
fmt.Printf("User's MENTION TIMELINE:\n%+v\n", tweets)
|
||||
|
||||
// Retweets of Me Timeline
|
||||
retweetTimelineParams := &twitter.RetweetsOfMeTimelineParams{
|
||||
Count: 2,
|
||||
TweetMode: "extended",
|
||||
}
|
||||
tweets, _, _ = client.Timelines.RetweetsOfMeTimeline(retweetTimelineParams)
|
||||
fmt.Printf("User's 'RETWEETS OF ME' TIMELINE:\n%+v\n", tweets)
|
||||
|
||||
// Update (POST!) Tweet (uncomment to run)
|
||||
// tweet, _, _ := client.Statuses.Update("just setting up my twttr", nil)
|
||||
// fmt.Printf("Posted Tweet\n%v\n", tweet)
|
||||
}
|
||||
23
vendor/github.com/dghubble/go-twitter/test
generated
vendored
Executable file
23
vendor/github.com/dghubble/go-twitter/test
generated
vendored
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
PKGS=$(go list ./... | grep -v /examples)
|
||||
FORMATTABLE="$(ls -d */)"
|
||||
LINTABLE=$(go list ./...)
|
||||
|
||||
go test $PKGS -cover
|
||||
go vet $PKGS
|
||||
|
||||
echo "Checking gofmt..."
|
||||
fmtRes=$(gofmt -l $FORMATTABLE)
|
||||
if [ -n "${fmtRes}" ]; then
|
||||
echo -e "gofmt checking failed:\n${fmtRes}"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "Checking golint..."
|
||||
lintRes=$(echo $LINTABLE | xargs -n 1 golint)
|
||||
if [ -n "${lintRes}" ]; then
|
||||
echo -e "golint checking failed:\n${lintRes}"
|
||||
exit 2
|
||||
fi
|
||||
37
vendor/github.com/dghubble/go-twitter/twitter/accounts.go
generated
vendored
Normal file
37
vendor/github.com/dghubble/go-twitter/twitter/accounts.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dghubble/sling"
|
||||
)
|
||||
|
||||
// AccountService provides a method for account credential verification.
|
||||
type AccountService struct {
|
||||
sling *sling.Sling
|
||||
}
|
||||
|
||||
// newAccountService returns a new AccountService.
|
||||
func newAccountService(sling *sling.Sling) *AccountService {
|
||||
return &AccountService{
|
||||
sling: sling.Path("account/"),
|
||||
}
|
||||
}
|
||||
|
||||
// AccountVerifyParams are the params for AccountService.VerifyCredentials.
|
||||
type AccountVerifyParams struct {
|
||||
IncludeEntities *bool `url:"include_entities,omitempty"`
|
||||
SkipStatus *bool `url:"skip_status,omitempty"`
|
||||
IncludeEmail *bool `url:"include_email,omitempty"`
|
||||
}
|
||||
|
||||
// VerifyCredentials returns the authorized user if credentials are valid and
|
||||
// returns an error otherwise.
|
||||
// Requires a user auth context.
|
||||
// https://dev.twitter.com/rest/reference/get/account/verify_credentials
|
||||
func (s *AccountService) VerifyCredentials(params *AccountVerifyParams) (*User, *http.Response, error) {
|
||||
user := new(User)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("verify_credentials.json").QueryStruct(params).Receive(user, apiError)
|
||||
return user, resp, relevantError(err, *apiError)
|
||||
}
|
||||
27
vendor/github.com/dghubble/go-twitter/twitter/accounts_test.go
generated
vendored
Normal file
27
vendor/github.com/dghubble/go-twitter/twitter/accounts_test.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAccountService_VerifyCredentials(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/account/verify_credentials.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"include_entities": "false", "include_email": "true"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"name": "Dalton Hubble", "id": 623265148}`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
user, _, err := client.Accounts.VerifyCredentials(&AccountVerifyParams{IncludeEntities: Bool(false), IncludeEmail: Bool(true)})
|
||||
expected := &User{Name: "Dalton Hubble", ID: 623265148}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, user)
|
||||
}
|
||||
25
vendor/github.com/dghubble/go-twitter/twitter/backoffs.go
generated
vendored
Normal file
25
vendor/github.com/dghubble/go-twitter/twitter/backoffs.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
)
|
||||
|
||||
func newExponentialBackOff() *backoff.ExponentialBackOff {
|
||||
b := backoff.NewExponentialBackOff()
|
||||
b.InitialInterval = 5 * time.Second
|
||||
b.Multiplier = 2.0
|
||||
b.MaxInterval = 320 * time.Second
|
||||
b.Reset()
|
||||
return b
|
||||
}
|
||||
|
||||
func newAggressiveExponentialBackOff() *backoff.ExponentialBackOff {
|
||||
b := backoff.NewExponentialBackOff()
|
||||
b.InitialInterval = 1 * time.Minute
|
||||
b.Multiplier = 2.0
|
||||
b.MaxInterval = 16 * time.Minute
|
||||
b.Reset()
|
||||
return b
|
||||
}
|
||||
37
vendor/github.com/dghubble/go-twitter/twitter/backoffs_test.go
generated
vendored
Normal file
37
vendor/github.com/dghubble/go-twitter/twitter/backoffs_test.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewExponentialBackOff(t *testing.T) {
|
||||
b := newExponentialBackOff()
|
||||
assert.Equal(t, 5*time.Second, b.InitialInterval)
|
||||
assert.Equal(t, 2.0, b.Multiplier)
|
||||
assert.Equal(t, 320*time.Second, b.MaxInterval)
|
||||
}
|
||||
|
||||
func TestNewAggressiveExponentialBackOff(t *testing.T) {
|
||||
b := newAggressiveExponentialBackOff()
|
||||
assert.Equal(t, 1*time.Minute, b.InitialInterval)
|
||||
assert.Equal(t, 2.0, b.Multiplier)
|
||||
assert.Equal(t, 16*time.Minute, b.MaxInterval)
|
||||
}
|
||||
|
||||
// BackoffRecorder is an implementation of backoff.BackOff that records
|
||||
// calls to NextBackOff and Reset for later inspection in tests.
|
||||
type BackOffRecorder struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
func (b *BackOffRecorder) NextBackOff() time.Duration {
|
||||
b.Count++
|
||||
return 1 * time.Nanosecond
|
||||
}
|
||||
|
||||
func (b *BackOffRecorder) Reset() {
|
||||
b.Count = 0
|
||||
}
|
||||
88
vendor/github.com/dghubble/go-twitter/twitter/demux.go
generated
vendored
Normal file
88
vendor/github.com/dghubble/go-twitter/twitter/demux.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
package twitter
|
||||
|
||||
// A Demux receives interface{} messages individually or from a channel and
|
||||
// sends those messages to one or more outputs determined by the
|
||||
// implementation.
|
||||
type Demux interface {
|
||||
Handle(message interface{})
|
||||
HandleChan(messages <-chan interface{})
|
||||
}
|
||||
|
||||
// SwitchDemux receives messages and uses a type switch to send each typed
|
||||
// message to a handler function.
|
||||
type SwitchDemux struct {
|
||||
All func(message interface{})
|
||||
Tweet func(tweet *Tweet)
|
||||
DM func(dm *DirectMessage)
|
||||
StatusDeletion func(deletion *StatusDeletion)
|
||||
LocationDeletion func(LocationDeletion *LocationDeletion)
|
||||
StreamLimit func(limit *StreamLimit)
|
||||
StatusWithheld func(statusWithheld *StatusWithheld)
|
||||
UserWithheld func(userWithheld *UserWithheld)
|
||||
StreamDisconnect func(disconnect *StreamDisconnect)
|
||||
Warning func(warning *StallWarning)
|
||||
FriendsList func(friendsList *FriendsList)
|
||||
Event func(event *Event)
|
||||
Other func(message interface{})
|
||||
}
|
||||
|
||||
// NewSwitchDemux returns a new SwitchMux which has NoOp handler functions.
|
||||
func NewSwitchDemux() SwitchDemux {
|
||||
return SwitchDemux{
|
||||
All: func(message interface{}) {},
|
||||
Tweet: func(tweet *Tweet) {},
|
||||
DM: func(dm *DirectMessage) {},
|
||||
StatusDeletion: func(deletion *StatusDeletion) {},
|
||||
LocationDeletion: func(LocationDeletion *LocationDeletion) {},
|
||||
StreamLimit: func(limit *StreamLimit) {},
|
||||
StatusWithheld: func(statusWithheld *StatusWithheld) {},
|
||||
UserWithheld: func(userWithheld *UserWithheld) {},
|
||||
StreamDisconnect: func(disconnect *StreamDisconnect) {},
|
||||
Warning: func(warning *StallWarning) {},
|
||||
FriendsList: func(friendsList *FriendsList) {},
|
||||
Event: func(event *Event) {},
|
||||
Other: func(message interface{}) {},
|
||||
}
|
||||
}
|
||||
|
||||
// Handle determines the type of a message and calls the corresponding receiver
|
||||
// function with the typed message. All messages are passed to the All func.
|
||||
// Messages with unmatched types are passed to the Other func.
|
||||
func (d SwitchDemux) Handle(message interface{}) {
|
||||
d.All(message)
|
||||
switch msg := message.(type) {
|
||||
case *Tweet:
|
||||
d.Tweet(msg)
|
||||
case *DirectMessage:
|
||||
d.DM(msg)
|
||||
case *StatusDeletion:
|
||||
d.StatusDeletion(msg)
|
||||
case *LocationDeletion:
|
||||
d.LocationDeletion(msg)
|
||||
case *StreamLimit:
|
||||
d.StreamLimit(msg)
|
||||
case *StatusWithheld:
|
||||
d.StatusWithheld(msg)
|
||||
case *UserWithheld:
|
||||
d.UserWithheld(msg)
|
||||
case *StreamDisconnect:
|
||||
d.StreamDisconnect(msg)
|
||||
case *StallWarning:
|
||||
d.Warning(msg)
|
||||
case *FriendsList:
|
||||
d.FriendsList(msg)
|
||||
case *Event:
|
||||
d.Event(msg)
|
||||
default:
|
||||
d.Other(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleChan receives messages and calls the corresponding receiver function
|
||||
// with the typed message. All messages are passed to the All func. Messages
|
||||
// with unmatched type are passed to the Other func.
|
||||
func (d SwitchDemux) HandleChan(messages <-chan interface{}) {
|
||||
for message := range messages {
|
||||
d.Handle(message)
|
||||
}
|
||||
}
|
||||
135
vendor/github.com/dghubble/go-twitter/twitter/demux_test.go
generated
vendored
Normal file
135
vendor/github.com/dghubble/go-twitter/twitter/demux_test.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDemux_Handle(t *testing.T) {
|
||||
messages, expectedCounts := exampleMessages()
|
||||
counts := &counter{}
|
||||
demux := newCounterDemux(counts)
|
||||
for _, message := range messages {
|
||||
demux.Handle(message)
|
||||
}
|
||||
assert.Equal(t, expectedCounts, counts)
|
||||
}
|
||||
|
||||
func TestDemux_HandleChan(t *testing.T) {
|
||||
messages, expectedCounts := exampleMessages()
|
||||
counts := &counter{}
|
||||
demux := newCounterDemux(counts)
|
||||
ch := make(chan interface{})
|
||||
// stream messages into channel
|
||||
go func() {
|
||||
for _, msg := range messages {
|
||||
ch <- msg
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
// handle channel messages until exhausted
|
||||
demux.HandleChan(ch)
|
||||
assert.Equal(t, expectedCounts, counts)
|
||||
}
|
||||
|
||||
// counter counts stream messages by type for testing.
|
||||
type counter struct {
|
||||
all int
|
||||
tweet int
|
||||
dm int
|
||||
statusDeletion int
|
||||
locationDeletion int
|
||||
streamLimit int
|
||||
statusWithheld int
|
||||
userWithheld int
|
||||
streamDisconnect int
|
||||
stallWarning int
|
||||
friendsList int
|
||||
event int
|
||||
other int
|
||||
}
|
||||
|
||||
// newCounterDemux returns a Demux which counts message types.
|
||||
func newCounterDemux(counter *counter) Demux {
|
||||
demux := NewSwitchDemux()
|
||||
demux.All = func(interface{}) {
|
||||
counter.all++
|
||||
}
|
||||
demux.Tweet = func(*Tweet) {
|
||||
counter.tweet++
|
||||
}
|
||||
demux.DM = func(*DirectMessage) {
|
||||
counter.dm++
|
||||
}
|
||||
demux.StatusDeletion = func(*StatusDeletion) {
|
||||
counter.statusDeletion++
|
||||
}
|
||||
demux.LocationDeletion = func(*LocationDeletion) {
|
||||
counter.locationDeletion++
|
||||
}
|
||||
demux.StreamLimit = func(*StreamLimit) {
|
||||
counter.streamLimit++
|
||||
}
|
||||
demux.StatusWithheld = func(*StatusWithheld) {
|
||||
counter.statusWithheld++
|
||||
}
|
||||
demux.UserWithheld = func(*UserWithheld) {
|
||||
counter.userWithheld++
|
||||
}
|
||||
demux.StreamDisconnect = func(*StreamDisconnect) {
|
||||
counter.streamDisconnect++
|
||||
}
|
||||
demux.Warning = func(*StallWarning) {
|
||||
counter.stallWarning++
|
||||
}
|
||||
demux.FriendsList = func(*FriendsList) {
|
||||
counter.friendsList++
|
||||
}
|
||||
demux.Event = func(*Event) {
|
||||
counter.event++
|
||||
}
|
||||
demux.Other = func(interface{}) {
|
||||
counter.other++
|
||||
}
|
||||
return demux
|
||||
}
|
||||
|
||||
// examples messages returns a test stream of messages and the expected
|
||||
// counts of each message type.
|
||||
func exampleMessages() (messages []interface{}, expectedCounts *counter) {
|
||||
var (
|
||||
tweet = &Tweet{}
|
||||
dm = &DirectMessage{}
|
||||
statusDeletion = &StatusDeletion{}
|
||||
locationDeletion = &LocationDeletion{}
|
||||
streamLimit = &StreamLimit{}
|
||||
statusWithheld = &StatusWithheld{}
|
||||
userWithheld = &UserWithheld{}
|
||||
streamDisconnect = &StreamDisconnect{}
|
||||
stallWarning = &StallWarning{}
|
||||
friendsList = &FriendsList{}
|
||||
event = &Event{}
|
||||
otherA = func() {}
|
||||
otherB = struct{}{}
|
||||
)
|
||||
messages = []interface{}{tweet, dm, statusDeletion, locationDeletion,
|
||||
streamLimit, statusWithheld, userWithheld, streamDisconnect,
|
||||
stallWarning, friendsList, event, otherA, otherB}
|
||||
expectedCounts = &counter{
|
||||
all: len(messages),
|
||||
tweet: 1,
|
||||
dm: 1,
|
||||
statusDeletion: 1,
|
||||
locationDeletion: 1,
|
||||
streamLimit: 1,
|
||||
statusWithheld: 1,
|
||||
userWithheld: 1,
|
||||
streamDisconnect: 1,
|
||||
stallWarning: 1,
|
||||
friendsList: 1,
|
||||
event: 1,
|
||||
other: 2,
|
||||
}
|
||||
return messages, expectedCounts
|
||||
}
|
||||
130
vendor/github.com/dghubble/go-twitter/twitter/direct_messages.go
generated
vendored
Normal file
130
vendor/github.com/dghubble/go-twitter/twitter/direct_messages.go
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dghubble/sling"
|
||||
)
|
||||
|
||||
// DirectMessage is a direct message to a single recipient.
|
||||
type DirectMessage struct {
|
||||
CreatedAt string `json:"created_at"`
|
||||
Entities *Entities `json:"entities"`
|
||||
ID int64 `json:"id"`
|
||||
IDStr string `json:"id_str"`
|
||||
Recipient *User `json:"recipient"`
|
||||
RecipientID int64 `json:"recipient_id"`
|
||||
RecipientScreenName string `json:"recipient_screen_name"`
|
||||
Sender *User `json:"sender"`
|
||||
SenderID int64 `json:"sender_id"`
|
||||
SenderScreenName string `json:"sender_screen_name"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// DirectMessageService provides methods for accessing Twitter direct message
|
||||
// API endpoints.
|
||||
type DirectMessageService struct {
|
||||
baseSling *sling.Sling
|
||||
sling *sling.Sling
|
||||
}
|
||||
|
||||
// newDirectMessageService returns a new DirectMessageService.
|
||||
func newDirectMessageService(sling *sling.Sling) *DirectMessageService {
|
||||
return &DirectMessageService{
|
||||
baseSling: sling.New(),
|
||||
sling: sling.Path("direct_messages/"),
|
||||
}
|
||||
}
|
||||
|
||||
// directMessageShowParams are the parameters for DirectMessageService.Show
|
||||
type directMessageShowParams struct {
|
||||
ID int64 `url:"id,omitempty"`
|
||||
}
|
||||
|
||||
// Show returns the requested Direct Message.
|
||||
// Requires a user auth context with DM scope.
|
||||
// https://dev.twitter.com/rest/reference/get/direct_messages/show
|
||||
func (s *DirectMessageService) Show(id int64) (*DirectMessage, *http.Response, error) {
|
||||
params := &directMessageShowParams{ID: id}
|
||||
dm := new(DirectMessage)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("show.json").QueryStruct(params).Receive(dm, apiError)
|
||||
return dm, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// DirectMessageGetParams are the parameters for DirectMessageService.Get
|
||||
type DirectMessageGetParams struct {
|
||||
SinceID int64 `url:"since_id,omitempty"`
|
||||
MaxID int64 `url:"max_id,omitempty"`
|
||||
Count int `url:"count,omitempty"`
|
||||
IncludeEntities *bool `url:"include_entities,omitempty"`
|
||||
SkipStatus *bool `url:"skip_status,omitempty"`
|
||||
}
|
||||
|
||||
// Get returns recent Direct Messages received by the authenticated user.
|
||||
// Requires a user auth context with DM scope.
|
||||
// https://dev.twitter.com/rest/reference/get/direct_messages
|
||||
func (s *DirectMessageService) Get(params *DirectMessageGetParams) ([]DirectMessage, *http.Response, error) {
|
||||
dms := new([]DirectMessage)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.baseSling.New().Get("direct_messages.json").QueryStruct(params).Receive(dms, apiError)
|
||||
return *dms, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// DirectMessageSentParams are the parameters for DirectMessageService.Sent
|
||||
type DirectMessageSentParams struct {
|
||||
SinceID int64 `url:"since_id,omitempty"`
|
||||
MaxID int64 `url:"max_id,omitempty"`
|
||||
Count int `url:"count,omitempty"`
|
||||
Page int `url:"page,omitempty"`
|
||||
IncludeEntities *bool `url:"include_entities,omitempty"`
|
||||
}
|
||||
|
||||
// Sent returns recent Direct Messages sent by the authenticated user.
|
||||
// Requires a user auth context with DM scope.
|
||||
// https://dev.twitter.com/rest/reference/get/direct_messages/sent
|
||||
func (s *DirectMessageService) Sent(params *DirectMessageSentParams) ([]DirectMessage, *http.Response, error) {
|
||||
dms := new([]DirectMessage)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("sent.json").QueryStruct(params).Receive(dms, apiError)
|
||||
return *dms, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// DirectMessageNewParams are the parameters for DirectMessageService.New
|
||||
type DirectMessageNewParams struct {
|
||||
UserID int64 `url:"user_id,omitempty"`
|
||||
ScreenName string `url:"screen_name,omitempty"`
|
||||
Text string `url:"text"`
|
||||
}
|
||||
|
||||
// New sends a new Direct Message to a specified user as the authenticated
|
||||
// user.
|
||||
// Requires a user auth context with DM scope.
|
||||
// https://dev.twitter.com/rest/reference/post/direct_messages/new
|
||||
func (s *DirectMessageService) New(params *DirectMessageNewParams) (*DirectMessage, *http.Response, error) {
|
||||
dm := new(DirectMessage)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Post("new.json").BodyForm(params).Receive(dm, apiError)
|
||||
return dm, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// DirectMessageDestroyParams are the parameters for DirectMessageService.Destroy
|
||||
type DirectMessageDestroyParams struct {
|
||||
ID int64 `url:"id,omitempty"`
|
||||
IncludeEntities *bool `url:"include_entities,omitempty"`
|
||||
}
|
||||
|
||||
// Destroy deletes the Direct Message with the given id and returns it if
|
||||
// successful.
|
||||
// Requires a user auth context with DM scope.
|
||||
// https://dev.twitter.com/rest/reference/post/direct_messages/destroy
|
||||
func (s *DirectMessageService) Destroy(id int64, params *DirectMessageDestroyParams) (*DirectMessage, *http.Response, error) {
|
||||
if params == nil {
|
||||
params = &DirectMessageDestroyParams{}
|
||||
}
|
||||
params.ID = id
|
||||
dm := new(DirectMessage)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Post("destroy.json").BodyForm(params).Receive(dm, apiError)
|
||||
return dm, resp, relevantError(err, *apiError)
|
||||
}
|
||||
110
vendor/github.com/dghubble/go-twitter/twitter/direct_messages_test.go
generated
vendored
Normal file
110
vendor/github.com/dghubble/go-twitter/twitter/direct_messages_test.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
testDM = DirectMessage{
|
||||
ID: 240136858829479936,
|
||||
Recipient: &User{ScreenName: "theSeanCook"},
|
||||
Sender: &User{ScreenName: "s0c1alm3dia"},
|
||||
Text: "hello world",
|
||||
}
|
||||
testDMIDStr = "240136858829479936"
|
||||
testDMJSON = `{"id": 240136858829479936,"recipient": {"screen_name": "theSeanCook"},"sender": {"screen_name": "s0c1alm3dia"},"text": "hello world"}`
|
||||
)
|
||||
|
||||
func TestDirectMessageService_Show(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/direct_messages/show.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"id": testDMIDStr}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, testDMJSON)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
dms, _, err := client.DirectMessages.Show(testDM.ID)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, &testDM, dms)
|
||||
}
|
||||
|
||||
func TestDirectMessageService_Get(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/direct_messages.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"since_id": "589147592367431680", "count": "1"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[`+testDMJSON+`]`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &DirectMessageGetParams{SinceID: 589147592367431680, Count: 1}
|
||||
dms, _, err := client.DirectMessages.Get(params)
|
||||
expected := []DirectMessage{testDM}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, dms)
|
||||
}
|
||||
|
||||
func TestDirectMessageService_Sent(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/direct_messages/sent.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"since_id": "589147592367431680", "count": "1"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[`+testDMJSON+`]`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &DirectMessageSentParams{SinceID: 589147592367431680, Count: 1}
|
||||
dms, _, err := client.DirectMessages.Sent(params)
|
||||
expected := []DirectMessage{testDM}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, dms)
|
||||
}
|
||||
|
||||
func TestDirectMessageService_New(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/direct_messages/new.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "POST", r)
|
||||
assertPostForm(t, map[string]string{"screen_name": "theseancook", "text": "hello world"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, testDMJSON)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &DirectMessageNewParams{ScreenName: "theseancook", Text: "hello world"}
|
||||
dm, _, err := client.DirectMessages.New(params)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, &testDM, dm)
|
||||
}
|
||||
|
||||
func TestDirectMessageService_Destroy(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/direct_messages/destroy.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "POST", r)
|
||||
assertPostForm(t, map[string]string{"id": testDMIDStr}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, testDMJSON)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
dm, _, err := client.DirectMessages.Destroy(testDM.ID, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, &testDM, dm)
|
||||
}
|
||||
70
vendor/github.com/dghubble/go-twitter/twitter/doc.go
generated
vendored
Normal file
70
vendor/github.com/dghubble/go-twitter/twitter/doc.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Package twitter provides a Client for the Twitter API.
|
||||
|
||||
|
||||
The twitter package provides a Client for accessing the Twitter API. Here are
|
||||
some example requests.
|
||||
|
||||
// Twitter client
|
||||
client := twitter.NewClient(httpClient)
|
||||
// Home Timeline
|
||||
tweets, resp, err := client.Timelines.HomeTimeline(&HomeTimelineParams{})
|
||||
// Send a Tweet
|
||||
tweet, resp, err := client.Statuses.Update("just setting up my twttr", nil)
|
||||
// Status Show
|
||||
tweet, resp, err := client.Statuses.Show(585613041028431872, nil)
|
||||
// User Show
|
||||
params := &twitter.UserShowParams{ScreenName: "dghubble"}
|
||||
user, resp, err := client.Users.Show(params)
|
||||
// Followers
|
||||
followers, resp, err := client.Followers.List(&FollowerListParams{})
|
||||
|
||||
Required parameters are passed as positional arguments. Optional parameters
|
||||
are passed in a typed params struct (or pass nil).
|
||||
|
||||
Authentication
|
||||
|
||||
By design, the Twitter Client accepts any http.Client so user auth (OAuth1) or
|
||||
application auth (OAuth2) requests can be made by using the appropriate
|
||||
authenticated client. Use the https://github.com/dghubble/oauth1 and
|
||||
https://github.com/golang/oauth2 packages to obtain an http.Client which
|
||||
transparently authorizes requests.
|
||||
|
||||
For example, make requests as a consumer application on behalf of a user who
|
||||
has granted access, with OAuth1.
|
||||
|
||||
// OAuth1
|
||||
import (
|
||||
"github.com/dghubble/go-twitter/twitter"
|
||||
"github.com/dghubble/oauth1"
|
||||
)
|
||||
|
||||
config := oauth1.NewConfig("consumerKey", "consumerSecret")
|
||||
token := oauth1.NewToken("accessToken", "accessSecret")
|
||||
// http.Client will automatically authorize Requests
|
||||
httpClient := config.Client(oauth1.NoContext, token)
|
||||
|
||||
// twitter client
|
||||
client := twitter.NewClient(httpClient)
|
||||
|
||||
If no user auth context is needed, make requests as your application with
|
||||
application auth.
|
||||
|
||||
// OAuth2
|
||||
import (
|
||||
"github.com/dghubble/go-twitter/twitter"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
config := &oauth2.Config{}
|
||||
token := &oauth2.Token{AccessToken: accessToken}
|
||||
// http.Client will automatically authorize Requests
|
||||
httpClient := config.Client(oauth2.NoContext, token)
|
||||
|
||||
// twitter client
|
||||
client := twitter.NewClient(httpClient)
|
||||
|
||||
To implement Login with Twitter, see https://github.com/dghubble/gologin.
|
||||
|
||||
*/
|
||||
package twitter
|
||||
104
vendor/github.com/dghubble/go-twitter/twitter/entities.go
generated
vendored
Normal file
104
vendor/github.com/dghubble/go-twitter/twitter/entities.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
package twitter
|
||||
|
||||
// Entities represent metadata and context info parsed from Twitter components.
|
||||
// https://dev.twitter.com/overview/api/entities
|
||||
// TODO: symbols
|
||||
type Entities struct {
|
||||
Hashtags []HashtagEntity `json:"hashtags"`
|
||||
Media []MediaEntity `json:"media"`
|
||||
Urls []URLEntity `json:"urls"`
|
||||
UserMentions []MentionEntity `json:"user_mentions"`
|
||||
}
|
||||
|
||||
// HashtagEntity represents a hashtag which has been parsed from text.
|
||||
type HashtagEntity struct {
|
||||
Indices Indices `json:"indices"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// URLEntity represents a URL which has been parsed from text.
|
||||
type URLEntity struct {
|
||||
Indices Indices `json:"indices"`
|
||||
DisplayURL string `json:"display_url"`
|
||||
ExpandedURL string `json:"expanded_url"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// MediaEntity represents media elements associated with a Tweet.
|
||||
type MediaEntity struct {
|
||||
URLEntity
|
||||
ID int64 `json:"id"`
|
||||
IDStr string `json:"id_str"`
|
||||
MediaURL string `json:"media_url"`
|
||||
MediaURLHttps string `json:"media_url_https"`
|
||||
SourceStatusID int64 `json:"source_status_id"`
|
||||
SourceStatusIDStr string `json:"source_status_id_str"`
|
||||
Type string `json:"type"`
|
||||
Sizes MediaSizes `json:"sizes"`
|
||||
VideoInfo VideoInfo `json:"video_info"`
|
||||
}
|
||||
|
||||
// MentionEntity represents Twitter user mentions parsed from text.
|
||||
type MentionEntity struct {
|
||||
Indices Indices `json:"indices"`
|
||||
ID int64 `json:"id"`
|
||||
IDStr string `json:"id_str"`
|
||||
Name string `json:"name"`
|
||||
ScreenName string `json:"screen_name"`
|
||||
}
|
||||
|
||||
// UserEntities contain Entities parsed from User url and description fields.
|
||||
// https://dev.twitter.com/overview/api/entities-in-twitter-objects#users
|
||||
type UserEntities struct {
|
||||
URL Entities `json:"url"`
|
||||
Description Entities `json:"description"`
|
||||
}
|
||||
|
||||
// ExtendedEntity contains media information.
|
||||
// https://dev.twitter.com/overview/api/entities-in-twitter-objects#extended_entities
|
||||
type ExtendedEntity struct {
|
||||
Media []MediaEntity `json:"media"`
|
||||
}
|
||||
|
||||
// Indices represent the start and end offsets within text.
|
||||
type Indices [2]int
|
||||
|
||||
// Start returns the index at which an entity starts, inclusive.
|
||||
func (i Indices) Start() int {
|
||||
return i[0]
|
||||
}
|
||||
|
||||
// End returns the index at which an entity ends, exclusive.
|
||||
func (i Indices) End() int {
|
||||
return i[1]
|
||||
}
|
||||
|
||||
// MediaSizes contain the different size media that are available.
|
||||
// https://dev.twitter.com/overview/api/entities#obj-sizes
|
||||
type MediaSizes struct {
|
||||
Thumb MediaSize `json:"thumb"`
|
||||
Large MediaSize `json:"large"`
|
||||
Medium MediaSize `json:"medium"`
|
||||
Small MediaSize `json:"small"`
|
||||
}
|
||||
|
||||
// MediaSize describes the height, width, and resizing method used.
|
||||
type MediaSize struct {
|
||||
Width int `json:"w"`
|
||||
Height int `json:"h"`
|
||||
Resize string `json:"resize"`
|
||||
}
|
||||
|
||||
// VideoInfo is available on video media objects.
|
||||
type VideoInfo struct {
|
||||
AspectRatio [2]int `json:"aspect_ratio"`
|
||||
DurationMillis int `json:"duration_millis"`
|
||||
Variants []VideoVariant `json:"variants"`
|
||||
}
|
||||
|
||||
// VideoVariant describes one of the available video formats.
|
||||
type VideoVariant struct {
|
||||
ContentType string `json:"content_type"`
|
||||
Bitrate int `json:"bitrate"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
22
vendor/github.com/dghubble/go-twitter/twitter/entities_test.go
generated
vendored
Normal file
22
vendor/github.com/dghubble/go-twitter/twitter/entities_test.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIndices(t *testing.T) {
|
||||
cases := []struct {
|
||||
pair Indices
|
||||
expectedStart int
|
||||
expectedEnd int
|
||||
}{
|
||||
{Indices{}, 0, 0},
|
||||
{Indices{25, 47}, 25, 47},
|
||||
}
|
||||
for _, c := range cases {
|
||||
assert.Equal(t, c.expectedStart, c.pair.Start())
|
||||
assert.Equal(t, c.expectedEnd, c.pair.End())
|
||||
}
|
||||
}
|
||||
47
vendor/github.com/dghubble/go-twitter/twitter/errors.go
generated
vendored
Normal file
47
vendor/github.com/dghubble/go-twitter/twitter/errors.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// APIError represents a Twitter API Error response
|
||||
// https://dev.twitter.com/overview/api/response-codes
|
||||
type APIError struct {
|
||||
Errors []ErrorDetail `json:"errors"`
|
||||
}
|
||||
|
||||
// ErrorDetail represents an individual item in an APIError.
|
||||
type ErrorDetail struct {
|
||||
Message string `json:"message"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
func (e APIError) Error() string {
|
||||
if len(e.Errors) > 0 {
|
||||
err := e.Errors[0]
|
||||
return fmt.Sprintf("twitter: %d %v", err.Code, err.Message)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Empty returns true if empty. Otherwise, at least 1 error message/code is
|
||||
// present and false is returned.
|
||||
func (e APIError) Empty() bool {
|
||||
if len(e.Errors) == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// relevantError returns any non-nil http-related error (creating the request,
|
||||
// getting the response, decoding) if any. If the decoded apiError is non-zero
|
||||
// the apiError is returned. Otherwise, no errors occurred, returns nil.
|
||||
func relevantError(httpError error, apiError APIError) error {
|
||||
if httpError != nil {
|
||||
return httpError
|
||||
}
|
||||
if apiError.Empty() {
|
||||
return nil
|
||||
}
|
||||
return apiError
|
||||
}
|
||||
48
vendor/github.com/dghubble/go-twitter/twitter/errors_test.go
generated
vendored
Normal file
48
vendor/github.com/dghubble/go-twitter/twitter/errors_test.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var errAPI = APIError{
|
||||
Errors: []ErrorDetail{
|
||||
ErrorDetail{Message: "Status is a duplicate", Code: 187},
|
||||
},
|
||||
}
|
||||
var errHTTP = fmt.Errorf("unknown host")
|
||||
|
||||
func TestAPIError_Error(t *testing.T) {
|
||||
err := APIError{}
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, "", err.Error())
|
||||
}
|
||||
if assert.Error(t, errAPI) {
|
||||
assert.Equal(t, "twitter: 187 Status is a duplicate", errAPI.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIError_Empty(t *testing.T) {
|
||||
err := APIError{}
|
||||
assert.True(t, err.Empty())
|
||||
assert.False(t, errAPI.Empty())
|
||||
}
|
||||
|
||||
func TestRelevantError(t *testing.T) {
|
||||
cases := []struct {
|
||||
httpError error
|
||||
apiError APIError
|
||||
expected error
|
||||
}{
|
||||
{nil, APIError{}, nil},
|
||||
{nil, errAPI, errAPI},
|
||||
{errHTTP, APIError{}, errHTTP},
|
||||
{errHTTP, errAPI, errHTTP},
|
||||
}
|
||||
for _, c := range cases {
|
||||
err := relevantError(c.httpError, c.apiError)
|
||||
assert.Equal(t, c.expected, err)
|
||||
}
|
||||
}
|
||||
72
vendor/github.com/dghubble/go-twitter/twitter/favorites.go
generated
vendored
Normal file
72
vendor/github.com/dghubble/go-twitter/twitter/favorites.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dghubble/sling"
|
||||
)
|
||||
|
||||
// FavoriteService provides methods for accessing Twitter favorite API endpoints.
|
||||
//
|
||||
// Note: the like action was known as favorite before November 3, 2015; the
|
||||
// historical naming remains in API methods and object properties.
|
||||
type FavoriteService struct {
|
||||
sling *sling.Sling
|
||||
}
|
||||
|
||||
// newFavoriteService returns a new FavoriteService.
|
||||
func newFavoriteService(sling *sling.Sling) *FavoriteService {
|
||||
return &FavoriteService{
|
||||
sling: sling.Path("favorites/"),
|
||||
}
|
||||
}
|
||||
|
||||
// FavoriteListParams are the parameters for FavoriteService.List.
|
||||
type FavoriteListParams struct {
|
||||
UserID int64 `url:"user_id,omitempty"`
|
||||
ScreenName string `url:"screen_name,omitempty"`
|
||||
Count int `url:"count,omitempty"`
|
||||
SinceID int64 `url:"since_id,omitempty"`
|
||||
MaxID int64 `url:"max_id,omitempty"`
|
||||
IncludeEntities *bool `url:"include_entities,omitempty"`
|
||||
TweetMode string `url:"tweet_mode,omitempty"`
|
||||
}
|
||||
|
||||
// List returns liked Tweets from the specified user.
|
||||
// https://dev.twitter.com/rest/reference/get/favorites/list
|
||||
func (s *FavoriteService) List(params *FavoriteListParams) ([]Tweet, *http.Response, error) {
|
||||
favorites := new([]Tweet)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("list.json").QueryStruct(params).Receive(favorites, apiError)
|
||||
return *favorites, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// FavoriteCreateParams are the parameters for FavoriteService.Create.
|
||||
type FavoriteCreateParams struct {
|
||||
ID int64 `url:"id,omitempty"`
|
||||
}
|
||||
|
||||
// Create favorites the specified tweet.
|
||||
// Requires a user auth context.
|
||||
// https://dev.twitter.com/rest/reference/post/favorites/create
|
||||
func (s *FavoriteService) Create(params *FavoriteCreateParams) (*Tweet, *http.Response, error) {
|
||||
tweet := new(Tweet)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Post("create.json").QueryStruct(params).Receive(tweet, apiError)
|
||||
return tweet, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// FavoriteDestroyParams are the parameters for FavoriteService.Destroy.
|
||||
type FavoriteDestroyParams struct {
|
||||
ID int64 `url:"id,omitempty"`
|
||||
}
|
||||
|
||||
// Destroy un-favorites the specified tweet.
|
||||
// Requires a user auth context.
|
||||
// https://dev.twitter.com/rest/reference/post/favorites/destroy
|
||||
func (s *FavoriteService) Destroy(params *FavoriteDestroyParams) (*Tweet, *http.Response, error) {
|
||||
tweet := new(Tweet)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Post("destroy.json").QueryStruct(params).Receive(tweet, apiError)
|
||||
return tweet, resp, relevantError(err, *apiError)
|
||||
}
|
||||
65
vendor/github.com/dghubble/go-twitter/twitter/favorites_test.go
generated
vendored
Normal file
65
vendor/github.com/dghubble/go-twitter/twitter/favorites_test.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFavoriteService_List(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/favorites/list.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"user_id": "113419064", "since_id": "101492475", "include_entities": "false"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[{"text": "Gophercon talks!"}, {"text": "Why gophers are so adorable"}]`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
tweets, _, err := client.Favorites.List(&FavoriteListParams{UserID: 113419064, SinceID: 101492475, IncludeEntities: Bool(false)})
|
||||
expected := []Tweet{Tweet{Text: "Gophercon talks!"}, Tweet{Text: "Why gophers are so adorable"}}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, tweets)
|
||||
}
|
||||
|
||||
func TestFavoriteService_Create(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/favorites/create.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "POST", r)
|
||||
assertPostForm(t, map[string]string{"id": "12345"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"id": 581980947630845953, "text": "very informative tweet"}`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &FavoriteCreateParams{ID: 12345}
|
||||
tweet, _, err := client.Favorites.Create(params)
|
||||
assert.Nil(t, err)
|
||||
expected := &Tweet{ID: 581980947630845953, Text: "very informative tweet"}
|
||||
assert.Equal(t, expected, tweet)
|
||||
}
|
||||
|
||||
func TestFavoriteService_Destroy(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/favorites/destroy.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "POST", r)
|
||||
assertPostForm(t, map[string]string{"id": "12345"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"id": 581980947630845953, "text": "very unhappy tweet"}`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &FavoriteDestroyParams{ID: 12345}
|
||||
tweet, _, err := client.Favorites.Destroy(params)
|
||||
assert.Nil(t, err)
|
||||
expected := &Tweet{ID: 581980947630845953, Text: "very unhappy tweet"}
|
||||
assert.Equal(t, expected, tweet)
|
||||
}
|
||||
73
vendor/github.com/dghubble/go-twitter/twitter/followers.go
generated
vendored
Normal file
73
vendor/github.com/dghubble/go-twitter/twitter/followers.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dghubble/sling"
|
||||
)
|
||||
|
||||
// FollowerIDs is a cursored collection of follower ids.
|
||||
type FollowerIDs struct {
|
||||
IDs []int64 `json:"ids"`
|
||||
NextCursor int64 `json:"next_cursor"`
|
||||
NextCursorStr string `json:"next_cursor_str"`
|
||||
PreviousCursor int64 `json:"previous_cursor"`
|
||||
PreviousCursorStr string `json:"previous_cursor_str"`
|
||||
}
|
||||
|
||||
// Followers is a cursored collection of followers.
|
||||
type Followers struct {
|
||||
Users []User `json:"users"`
|
||||
NextCursor int64 `json:"next_cursor"`
|
||||
NextCursorStr string `json:"next_cursor_str"`
|
||||
PreviousCursor int64 `json:"previous_cursor"`
|
||||
PreviousCursorStr string `json:"previous_cursor_str"`
|
||||
}
|
||||
|
||||
// FollowerService provides methods for accessing Twitter followers endpoints.
|
||||
type FollowerService struct {
|
||||
sling *sling.Sling
|
||||
}
|
||||
|
||||
// newFollowerService returns a new FollowerService.
|
||||
func newFollowerService(sling *sling.Sling) *FollowerService {
|
||||
return &FollowerService{
|
||||
sling: sling.Path("followers/"),
|
||||
}
|
||||
}
|
||||
|
||||
// FollowerIDParams are the parameters for FollowerService.Ids
|
||||
type FollowerIDParams struct {
|
||||
UserID int64 `url:"user_id,omitempty"`
|
||||
ScreenName string `url:"screen_name,omitempty"`
|
||||
Cursor int64 `url:"cursor,omitempty"`
|
||||
Count int `url:"count,omitempty"`
|
||||
}
|
||||
|
||||
// IDs returns a cursored collection of user ids following the specified user.
|
||||
// https://dev.twitter.com/rest/reference/get/followers/ids
|
||||
func (s *FollowerService) IDs(params *FollowerIDParams) (*FollowerIDs, *http.Response, error) {
|
||||
ids := new(FollowerIDs)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("ids.json").QueryStruct(params).Receive(ids, apiError)
|
||||
return ids, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// FollowerListParams are the parameters for FollowerService.List
|
||||
type FollowerListParams struct {
|
||||
UserID int64 `url:"user_id,omitempty"`
|
||||
ScreenName string `url:"screen_name,omitempty"`
|
||||
Cursor int64 `url:"cursor,omitempty"`
|
||||
Count int `url:"count,omitempty"`
|
||||
SkipStatus *bool `url:"skip_status,omitempty"`
|
||||
IncludeUserEntities *bool `url:"include_user_entities,omitempty"`
|
||||
}
|
||||
|
||||
// List returns a cursored collection of Users following the specified user.
|
||||
// https://dev.twitter.com/rest/reference/get/followers/list
|
||||
func (s *FollowerService) List(params *FollowerListParams) (*Followers, *http.Response, error) {
|
||||
followers := new(Followers)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("list.json").QueryStruct(params).Receive(followers, apiError)
|
||||
return followers, resp, relevantError(err, *apiError)
|
||||
}
|
||||
69
vendor/github.com/dghubble/go-twitter/twitter/followers_test.go
generated
vendored
Normal file
69
vendor/github.com/dghubble/go-twitter/twitter/followers_test.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFollowerService_Ids(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/followers/ids.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"user_id": "623265148", "count": "5", "cursor": "1516933260114270762"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"ids":[178082406,3318241001,1318020818,191714329,376703838],"next_cursor":1516837838944119498,"next_cursor_str":"1516837838944119498","previous_cursor":-1516924983503961435,"previous_cursor_str":"-1516924983503961435"}`)
|
||||
})
|
||||
expected := &FollowerIDs{
|
||||
IDs: []int64{178082406, 3318241001, 1318020818, 191714329, 376703838},
|
||||
NextCursor: 1516837838944119498,
|
||||
NextCursorStr: "1516837838944119498",
|
||||
PreviousCursor: -1516924983503961435,
|
||||
PreviousCursorStr: "-1516924983503961435",
|
||||
}
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &FollowerIDParams{
|
||||
UserID: 623265148,
|
||||
Count: 5,
|
||||
Cursor: 1516933260114270762,
|
||||
}
|
||||
followerIDs, _, err := client.Followers.IDs(params)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, followerIDs)
|
||||
}
|
||||
|
||||
func TestFollowerService_List(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/followers/list.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"screen_name": "dghubble", "count": "5", "cursor": "1516933260114270762", "skip_status": "true", "include_user_entities": "false"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"users": [{"id": 123}], "next_cursor":1516837838944119498,"next_cursor_str":"1516837838944119498","previous_cursor":-1516924983503961435,"previous_cursor_str":"-1516924983503961435"}`)
|
||||
})
|
||||
expected := &Followers{
|
||||
Users: []User{User{ID: 123}},
|
||||
NextCursor: 1516837838944119498,
|
||||
NextCursorStr: "1516837838944119498",
|
||||
PreviousCursor: -1516924983503961435,
|
||||
PreviousCursorStr: "-1516924983503961435",
|
||||
}
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &FollowerListParams{
|
||||
ScreenName: "dghubble",
|
||||
Count: 5,
|
||||
Cursor: 1516933260114270762,
|
||||
SkipStatus: Bool(true),
|
||||
IncludeUserEntities: Bool(false),
|
||||
}
|
||||
followers, _, err := client.Followers.List(params)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, followers)
|
||||
}
|
||||
73
vendor/github.com/dghubble/go-twitter/twitter/friends.go
generated
vendored
Normal file
73
vendor/github.com/dghubble/go-twitter/twitter/friends.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dghubble/sling"
|
||||
)
|
||||
|
||||
// FriendIDs is a cursored collection of friend ids.
|
||||
type FriendIDs struct {
|
||||
IDs []int64 `json:"ids"`
|
||||
NextCursor int64 `json:"next_cursor"`
|
||||
NextCursorStr string `json:"next_cursor_str"`
|
||||
PreviousCursor int64 `json:"previous_cursor"`
|
||||
PreviousCursorStr string `json:"previous_cursor_str"`
|
||||
}
|
||||
|
||||
// Friends is a cursored collection of friends.
|
||||
type Friends struct {
|
||||
Users []User `json:"users"`
|
||||
NextCursor int64 `json:"next_cursor"`
|
||||
NextCursorStr string `json:"next_cursor_str"`
|
||||
PreviousCursor int64 `json:"previous_cursor"`
|
||||
PreviousCursorStr string `json:"previous_cursor_str"`
|
||||
}
|
||||
|
||||
// FriendService provides methods for accessing Twitter friends endpoints.
|
||||
type FriendService struct {
|
||||
sling *sling.Sling
|
||||
}
|
||||
|
||||
// newFriendService returns a new FriendService.
|
||||
func newFriendService(sling *sling.Sling) *FriendService {
|
||||
return &FriendService{
|
||||
sling: sling.Path("friends/"),
|
||||
}
|
||||
}
|
||||
|
||||
// FriendIDParams are the parameters for FriendService.Ids
|
||||
type FriendIDParams struct {
|
||||
UserID int64 `url:"user_id,omitempty"`
|
||||
ScreenName string `url:"screen_name,omitempty"`
|
||||
Cursor int64 `url:"cursor,omitempty"`
|
||||
Count int `url:"count,omitempty"`
|
||||
}
|
||||
|
||||
// IDs returns a cursored collection of user ids that the specified user is following.
|
||||
// https://dev.twitter.com/rest/reference/get/friends/ids
|
||||
func (s *FriendService) IDs(params *FriendIDParams) (*FriendIDs, *http.Response, error) {
|
||||
ids := new(FriendIDs)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("ids.json").QueryStruct(params).Receive(ids, apiError)
|
||||
return ids, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// FriendListParams are the parameters for FriendService.List
|
||||
type FriendListParams struct {
|
||||
UserID int64 `url:"user_id,omitempty"`
|
||||
ScreenName string `url:"screen_name,omitempty"`
|
||||
Cursor int64 `url:"cursor,omitempty"`
|
||||
Count int `url:"count,omitempty"`
|
||||
SkipStatus *bool `url:"skip_status,omitempty"`
|
||||
IncludeUserEntities *bool `url:"include_user_entities,omitempty"`
|
||||
}
|
||||
|
||||
// List returns a cursored collection of Users that the specified user is following.
|
||||
// https://dev.twitter.com/rest/reference/get/friends/list
|
||||
func (s *FriendService) List(params *FriendListParams) (*Friends, *http.Response, error) {
|
||||
friends := new(Friends)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("list.json").QueryStruct(params).Receive(friends, apiError)
|
||||
return friends, resp, relevantError(err, *apiError)
|
||||
}
|
||||
69
vendor/github.com/dghubble/go-twitter/twitter/friends_test.go
generated
vendored
Normal file
69
vendor/github.com/dghubble/go-twitter/twitter/friends_test.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFriendService_Ids(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/friends/ids.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"user_id": "623265148", "count": "5", "cursor": "1516933260114270762"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"ids":[178082406,3318241001,1318020818,191714329,376703838],"next_cursor":1516837838944119498,"next_cursor_str":"1516837838944119498","previous_cursor":-1516924983503961435,"previous_cursor_str":"-1516924983503961435"}`)
|
||||
})
|
||||
expected := &FriendIDs{
|
||||
IDs: []int64{178082406, 3318241001, 1318020818, 191714329, 376703838},
|
||||
NextCursor: 1516837838944119498,
|
||||
NextCursorStr: "1516837838944119498",
|
||||
PreviousCursor: -1516924983503961435,
|
||||
PreviousCursorStr: "-1516924983503961435",
|
||||
}
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &FriendIDParams{
|
||||
UserID: 623265148,
|
||||
Count: 5,
|
||||
Cursor: 1516933260114270762,
|
||||
}
|
||||
friendIDs, _, err := client.Friends.IDs(params)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, friendIDs)
|
||||
}
|
||||
|
||||
func TestFriendService_List(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/friends/list.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"screen_name": "dghubble", "count": "5", "cursor": "1516933260114270762", "skip_status": "true", "include_user_entities": "false"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"users": [{"id": 123}], "next_cursor":1516837838944119498,"next_cursor_str":"1516837838944119498","previous_cursor":-1516924983503961435,"previous_cursor_str":"-1516924983503961435"}`)
|
||||
})
|
||||
expected := &Friends{
|
||||
Users: []User{User{ID: 123}},
|
||||
NextCursor: 1516837838944119498,
|
||||
NextCursorStr: "1516837838944119498",
|
||||
PreviousCursor: -1516924983503961435,
|
||||
PreviousCursorStr: "-1516924983503961435",
|
||||
}
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &FriendListParams{
|
||||
ScreenName: "dghubble",
|
||||
Count: 5,
|
||||
Cursor: 1516933260114270762,
|
||||
SkipStatus: Bool(true),
|
||||
IncludeUserEntities: Bool(false),
|
||||
}
|
||||
friends, _, err := client.Friends.List(params)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, friends)
|
||||
}
|
||||
134
vendor/github.com/dghubble/go-twitter/twitter/friendships.go
generated
vendored
Normal file
134
vendor/github.com/dghubble/go-twitter/twitter/friendships.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dghubble/sling"
|
||||
)
|
||||
|
||||
// FriendshipService provides methods for accessing Twitter friendship API
|
||||
// endpoints.
|
||||
type FriendshipService struct {
|
||||
sling *sling.Sling
|
||||
}
|
||||
|
||||
// newFriendshipService returns a new FriendshipService.
|
||||
func newFriendshipService(sling *sling.Sling) *FriendshipService {
|
||||
return &FriendshipService{
|
||||
sling: sling.Path("friendships/"),
|
||||
}
|
||||
}
|
||||
|
||||
// FriendshipCreateParams are parameters for FriendshipService.Create
|
||||
type FriendshipCreateParams struct {
|
||||
ScreenName string `url:"screen_name,omitempty"`
|
||||
UserID int64 `url:"user_id,omitempty"`
|
||||
Follow *bool `url:"follow,omitempty"`
|
||||
}
|
||||
|
||||
// Create creates a friendship to (i.e. follows) the specified user and
|
||||
// returns the followed user.
|
||||
// Requires a user auth context.
|
||||
// https://dev.twitter.com/rest/reference/post/friendships/create
|
||||
func (s *FriendshipService) Create(params *FriendshipCreateParams) (*User, *http.Response, error) {
|
||||
user := new(User)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Post("create.json").QueryStruct(params).Receive(user, apiError)
|
||||
return user, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// FriendshipShowParams are paramenters for FriendshipService.Show
|
||||
type FriendshipShowParams struct {
|
||||
SourceID int64 `url:"source_id,omitempty"`
|
||||
SourceScreenName string `url:"source_screen_name,omitempty"`
|
||||
TargetID int64 `url:"target_id,omitempty"`
|
||||
TargetScreenName string `url:"target_screen_name,omitempty"`
|
||||
}
|
||||
|
||||
// Show returns the relationship between two arbitrary users.
|
||||
// Requires a user auth or an app context.
|
||||
// https://dev.twitter.com/rest/reference/get/friendships/show
|
||||
func (s *FriendshipService) Show(params *FriendshipShowParams) (*Relationship, *http.Response, error) {
|
||||
response := new(RelationshipResponse)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("show.json").QueryStruct(params).Receive(response, apiError)
|
||||
return response.Relationship, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// RelationshipResponse contains a relationship.
|
||||
type RelationshipResponse struct {
|
||||
Relationship *Relationship `json:"relationship"`
|
||||
}
|
||||
|
||||
// Relationship represents the relation between a source user and target user.
|
||||
type Relationship struct {
|
||||
Source RelationshipSource `json:"source"`
|
||||
Target RelationshipTarget `json:"target"`
|
||||
}
|
||||
|
||||
// RelationshipSource represents the source user.
|
||||
type RelationshipSource struct {
|
||||
ID int64 `json:"id"`
|
||||
IDStr string `json:"id_str"`
|
||||
ScreenName string `json:"screen_name"`
|
||||
Following bool `json:"following"`
|
||||
FollowedBy bool `json:"followed_by"`
|
||||
CanDM bool `json:"can_dm"`
|
||||
Blocking bool `json:"blocking"`
|
||||
Muting bool `json:"muting"`
|
||||
AllReplies bool `json:"all_replies"`
|
||||
WantRetweets bool `json:"want_retweets"`
|
||||
MarkedSpam bool `json:"marked_spam"`
|
||||
NotificationsEnabled bool `json:"notifications_enabled"`
|
||||
}
|
||||
|
||||
// RelationshipTarget represents the target user.
|
||||
type RelationshipTarget struct {
|
||||
ID int64 `json:"id"`
|
||||
IDStr string `json:"id_str"`
|
||||
ScreenName string `json:"screen_name"`
|
||||
Following bool `json:"following"`
|
||||
FollowedBy bool `json:"followed_by"`
|
||||
}
|
||||
|
||||
// FriendshipDestroyParams are paramenters for FriendshipService.Destroy
|
||||
type FriendshipDestroyParams struct {
|
||||
ScreenName string `url:"screen_name,omitempty"`
|
||||
UserID int64 `url:"user_id,omitempty"`
|
||||
}
|
||||
|
||||
// Destroy destroys a friendship to (i.e. unfollows) the specified user and
|
||||
// returns the unfollowed user.
|
||||
// Requires a user auth context.
|
||||
// https://dev.twitter.com/rest/reference/post/friendships/destroy
|
||||
func (s *FriendshipService) Destroy(params *FriendshipDestroyParams) (*User, *http.Response, error) {
|
||||
user := new(User)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Post("destroy.json").QueryStruct(params).Receive(user, apiError)
|
||||
return user, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// FriendshipPendingParams are paramenters for FriendshipService.Outgoing
|
||||
type FriendshipPendingParams struct {
|
||||
Cursor int64 `url:"cursor,omitempty"`
|
||||
}
|
||||
|
||||
// Outgoing returns a collection of numeric IDs for every protected user for whom the authenticating
|
||||
// user has a pending follow request.
|
||||
// https://dev.twitter.com/rest/reference/get/friendships/outgoing
|
||||
func (s *FriendshipService) Outgoing(params *FriendshipPendingParams) (*FriendIDs, *http.Response, error) {
|
||||
ids := new(FriendIDs)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("outgoing.json").QueryStruct(params).Receive(ids, apiError)
|
||||
return ids, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// Incoming returns a collection of numeric IDs for every user who has a pending request to
|
||||
// follow the authenticating user.
|
||||
// https://dev.twitter.com/rest/reference/get/friendships/incoming
|
||||
func (s *FriendshipService) Incoming(params *FriendshipPendingParams) (*FriendIDs, *http.Response, error) {
|
||||
ids := new(FriendIDs)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("incoming.json").QueryStruct(params).Receive(ids, apiError)
|
||||
return ids, resp, relevantError(err, *apiError)
|
||||
}
|
||||
123
vendor/github.com/dghubble/go-twitter/twitter/friendships_test.go
generated
vendored
Normal file
123
vendor/github.com/dghubble/go-twitter/twitter/friendships_test.go
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFriendshipService_Create(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/friendships/create.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "POST", r)
|
||||
assertPostForm(t, map[string]string{"user_id": "12345"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"id": 12345, "name": "Doug Williams"}`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &FriendshipCreateParams{UserID: 12345}
|
||||
user, _, err := client.Friendships.Create(params)
|
||||
assert.Nil(t, err)
|
||||
expected := &User{ID: 12345, Name: "Doug Williams"}
|
||||
assert.Equal(t, expected, user)
|
||||
}
|
||||
|
||||
func TestFriendshipService_Show(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/friendships/show.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"source_screen_name": "foo", "target_screen_name": "bar"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{ "relationship": { "source": { "can_dm": false, "muting": true, "id_str": "8649302", "id": 8649302, "screen_name": "foo"}, "target": { "id_str": "12148", "id": 12148, "screen_name": "bar", "following": true, "followed_by": false } } }`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &FriendshipShowParams{SourceScreenName: "foo", TargetScreenName: "bar"}
|
||||
relationship, _, err := client.Friendships.Show(params)
|
||||
assert.Nil(t, err)
|
||||
expected := &Relationship{
|
||||
Source: RelationshipSource{ID: 8649302, ScreenName: "foo", IDStr: "8649302", CanDM: false, Muting: true, WantRetweets: false},
|
||||
Target: RelationshipTarget{ID: 12148, ScreenName: "bar", IDStr: "12148", Following: true, FollowedBy: false},
|
||||
}
|
||||
assert.Equal(t, expected, relationship)
|
||||
}
|
||||
|
||||
func TestFriendshipService_Destroy(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/friendships/destroy.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "POST", r)
|
||||
assertPostForm(t, map[string]string{"user_id": "12345"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"id": 12345, "name": "Doug Williams"}`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &FriendshipDestroyParams{UserID: 12345}
|
||||
user, _, err := client.Friendships.Destroy(params)
|
||||
assert.Nil(t, err)
|
||||
expected := &User{ID: 12345, Name: "Doug Williams"}
|
||||
assert.Equal(t, expected, user)
|
||||
}
|
||||
|
||||
func TestFriendshipService_Outgoing(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/friendships/outgoing.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"cursor": "1516933260114270762"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"ids":[178082406,3318241001,1318020818,191714329,376703838],"next_cursor":1516837838944119498,"next_cursor_str":"1516837838944119498","previous_cursor":-1516924983503961435,"previous_cursor_str":"-1516924983503961435"}`)
|
||||
})
|
||||
expected := &FriendIDs{
|
||||
IDs: []int64{178082406, 3318241001, 1318020818, 191714329, 376703838},
|
||||
NextCursor: 1516837838944119498,
|
||||
NextCursorStr: "1516837838944119498",
|
||||
PreviousCursor: -1516924983503961435,
|
||||
PreviousCursorStr: "-1516924983503961435",
|
||||
}
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &FriendshipPendingParams{
|
||||
Cursor: 1516933260114270762,
|
||||
}
|
||||
friendIDs, _, err := client.Friendships.Outgoing(params)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, friendIDs)
|
||||
}
|
||||
|
||||
func TestFriendshipService_Incoming(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/friendships/incoming.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"cursor": "1516933260114270762"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"ids":[178082406,3318241001,1318020818,191714329,376703838],"next_cursor":1516837838944119498,"next_cursor_str":"1516837838944119498","previous_cursor":-1516924983503961435,"previous_cursor_str":"-1516924983503961435"}`)
|
||||
})
|
||||
expected := &FriendIDs{
|
||||
IDs: []int64{178082406, 3318241001, 1318020818, 191714329, 376703838},
|
||||
NextCursor: 1516837838944119498,
|
||||
NextCursorStr: "1516837838944119498",
|
||||
PreviousCursor: -1516924983503961435,
|
||||
PreviousCursorStr: "-1516924983503961435",
|
||||
}
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &FriendshipPendingParams{
|
||||
Cursor: 1516933260114270762,
|
||||
}
|
||||
friendIDs, _, err := client.Friendships.Incoming(params)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, friendIDs)
|
||||
}
|
||||
62
vendor/github.com/dghubble/go-twitter/twitter/search.go
generated
vendored
Normal file
62
vendor/github.com/dghubble/go-twitter/twitter/search.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dghubble/sling"
|
||||
)
|
||||
|
||||
// Search represents the result of a Tweet search.
|
||||
type Search struct {
|
||||
Statuses []Tweet `json:"statuses"`
|
||||
Metadata *SearchMetadata `json:"search_metadata"`
|
||||
}
|
||||
|
||||
// SearchMetadata describes a Search result.
|
||||
type SearchMetadata struct {
|
||||
Count int `json:"count"`
|
||||
SinceID int64 `json:"since_id"`
|
||||
SinceIDStr string `json:"since_id_str"`
|
||||
MaxID int64 `json:"max_id"`
|
||||
MaxIDStr string `json:"max_id_str"`
|
||||
RefreshURL string `json:"refresh_url"`
|
||||
NextResults string `json:"next_results"`
|
||||
CompletedIn float64 `json:"completed_in"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
// SearchService provides methods for accessing Twitter search API endpoints.
|
||||
type SearchService struct {
|
||||
sling *sling.Sling
|
||||
}
|
||||
|
||||
// newSearchService returns a new SearchService.
|
||||
func newSearchService(sling *sling.Sling) *SearchService {
|
||||
return &SearchService{
|
||||
sling: sling.Path("search/"),
|
||||
}
|
||||
}
|
||||
|
||||
// SearchTweetParams are the parameters for SearchService.Tweets
|
||||
type SearchTweetParams struct {
|
||||
Query string `url:"q,omitempty"`
|
||||
Geocode string `url:"geocode,omitempty"`
|
||||
Lang string `url:"lang,omitempty"`
|
||||
Locale string `url:"locale,omitempty"`
|
||||
ResultType string `url:"result_type,omitempty"`
|
||||
Count int `url:"count,omitempty"`
|
||||
SinceID int64 `url:"since_id,omitempty"`
|
||||
MaxID int64 `url:"max_id,omitempty"`
|
||||
Until string `url:"until,omitempty"`
|
||||
IncludeEntities *bool `url:"include_entities,omitempty"`
|
||||
TweetMode string `url:"tweet_mode,omitempty"`
|
||||
}
|
||||
|
||||
// Tweets returns a collection of Tweets matching a search query.
|
||||
// https://dev.twitter.com/rest/reference/get/search/tweets
|
||||
func (s *SearchService) Tweets(params *SearchTweetParams) (*Search, *http.Response, error) {
|
||||
search := new(Search)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("tweets.json").QueryStruct(params).Receive(search, apiError)
|
||||
return search, resp, relevantError(err, *apiError)
|
||||
}
|
||||
46
vendor/github.com/dghubble/go-twitter/twitter/search_test.go
generated
vendored
Normal file
46
vendor/github.com/dghubble/go-twitter/twitter/search_test.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSearchService_Tweets(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/search/tweets.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"q": "happy birthday", "result_type": "popular", "count": "1"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"statuses":[{"id":781760642139250689}],"search_metadata":{"completed_in":0.043,"max_id":781760642139250689,"max_id_str":"781760642139250689","next_results":"?max_id=781760640104828927&q=happy+birthday&count=1&include_entities=1","query":"happy birthday","refresh_url":"?since_id=781760642139250689&q=happy+birthday&include_entities=1","count":1,"since_id":0,"since_id_str":"0"}}`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
search, _, err := client.Search.Tweets(&SearchTweetParams{
|
||||
Query: "happy birthday",
|
||||
Count: 1,
|
||||
ResultType: "popular",
|
||||
})
|
||||
expected := &Search{
|
||||
Statuses: []Tweet{
|
||||
Tweet{ID: 781760642139250689},
|
||||
},
|
||||
Metadata: &SearchMetadata{
|
||||
Count: 1,
|
||||
SinceID: 0,
|
||||
SinceIDStr: "0",
|
||||
MaxID: 781760642139250689,
|
||||
MaxIDStr: "781760642139250689",
|
||||
RefreshURL: "?since_id=781760642139250689&q=happy+birthday&include_entities=1",
|
||||
NextResults: "?max_id=781760640104828927&q=happy+birthday&count=1&include_entities=1",
|
||||
CompletedIn: 0.043,
|
||||
Query: "happy birthday",
|
||||
},
|
||||
}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, search)
|
||||
}
|
||||
303
vendor/github.com/dghubble/go-twitter/twitter/statuses.go
generated
vendored
Normal file
303
vendor/github.com/dghubble/go-twitter/twitter/statuses.go
generated
vendored
Normal file
@@ -0,0 +1,303 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/dghubble/sling"
|
||||
)
|
||||
|
||||
// Tweet represents a Twitter Tweet, previously called a status.
|
||||
// https://dev.twitter.com/overview/api/tweets
|
||||
// Deprecated fields: Contributors, Geo, Annotations
|
||||
type Tweet struct {
|
||||
Coordinates *Coordinates `json:"coordinates"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
CurrentUserRetweet *TweetIdentifier `json:"current_user_retweet"`
|
||||
Entities *Entities `json:"entities"`
|
||||
FavoriteCount int `json:"favorite_count"`
|
||||
Favorited bool `json:"favorited"`
|
||||
FilterLevel string `json:"filter_level"`
|
||||
ID int64 `json:"id"`
|
||||
IDStr string `json:"id_str"`
|
||||
InReplyToScreenName string `json:"in_reply_to_screen_name"`
|
||||
InReplyToStatusID int64 `json:"in_reply_to_status_id"`
|
||||
InReplyToStatusIDStr string `json:"in_reply_to_status_id_str"`
|
||||
InReplyToUserID int64 `json:"in_reply_to_user_id"`
|
||||
InReplyToUserIDStr string `json:"in_reply_to_user_id_str"`
|
||||
Lang string `json:"lang"`
|
||||
PossiblySensitive bool `json:"possibly_sensitive"`
|
||||
RetweetCount int `json:"retweet_count"`
|
||||
Retweeted bool `json:"retweeted"`
|
||||
RetweetedStatus *Tweet `json:"retweeted_status"`
|
||||
Source string `json:"source"`
|
||||
Scopes map[string]interface{} `json:"scopes"`
|
||||
Text string `json:"text"`
|
||||
FullText string `json:"full_text"`
|
||||
DisplayTextRange Indices `json:"display_text_range"`
|
||||
Place *Place `json:"place"`
|
||||
Truncated bool `json:"truncated"`
|
||||
User *User `json:"user"`
|
||||
WithheldCopyright bool `json:"withheld_copyright"`
|
||||
WithheldInCountries []string `json:"withheld_in_countries"`
|
||||
WithheldScope string `json:"withheld_scope"`
|
||||
ExtendedEntities *ExtendedEntity `json:"extended_entities"`
|
||||
ExtendedTweet *ExtendedTweet `json:"extended_tweet"`
|
||||
QuotedStatusID int64 `json:"quoted_status_id"`
|
||||
QuotedStatusIDStr string `json:"quoted_status_id_str"`
|
||||
QuotedStatus *Tweet `json:"quoted_status"`
|
||||
}
|
||||
|
||||
// ExtendedTweet represents fields embedded in extended Tweets when served in
|
||||
// compatibility mode (default).
|
||||
// https://dev.twitter.com/overview/api/upcoming-changes-to-tweets
|
||||
type ExtendedTweet struct {
|
||||
FullText string `json:"full_text"`
|
||||
DisplayTextRange Indices `json:"display_text_range"`
|
||||
}
|
||||
|
||||
// Place represents a Twitter Place / Location
|
||||
// https://dev.twitter.com/overview/api/places
|
||||
type Place struct {
|
||||
Attributes map[string]string `json:"attributes"`
|
||||
BoundingBox *BoundingBox `json:"bounding_box"`
|
||||
Country string `json:"country"`
|
||||
CountryCode string `json:"country_code"`
|
||||
FullName string `json:"full_name"`
|
||||
Geometry *BoundingBox `json:"geometry"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
PlaceType string `json:"place_type"`
|
||||
Polylines []string `json:"polylines"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// BoundingBox represents the bounding coordinates (longitude, latitutde)
|
||||
// defining the bounds of a box containing a Place entity.
|
||||
type BoundingBox struct {
|
||||
Coordinates [][][2]float64 `json:"coordinates"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Coordinates are pairs of longitude and latitude locations.
|
||||
type Coordinates struct {
|
||||
Coordinates [2]float64 `json:"coordinates"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// TweetIdentifier represents the id by which a Tweet can be identified.
|
||||
type TweetIdentifier struct {
|
||||
ID int64 `json:"id"`
|
||||
IDStr string `json:"id_str"`
|
||||
}
|
||||
|
||||
// StatusService provides methods for accessing Twitter status API endpoints.
|
||||
type StatusService struct {
|
||||
sling *sling.Sling
|
||||
}
|
||||
|
||||
// newStatusService returns a new StatusService.
|
||||
func newStatusService(sling *sling.Sling) *StatusService {
|
||||
return &StatusService{
|
||||
sling: sling.Path("statuses/"),
|
||||
}
|
||||
}
|
||||
|
||||
// StatusShowParams are the parameters for StatusService.Show
|
||||
type StatusShowParams struct {
|
||||
ID int64 `url:"id,omitempty"`
|
||||
TrimUser *bool `url:"trim_user,omitempty"`
|
||||
IncludeMyRetweet *bool `url:"include_my_retweet,omitempty"`
|
||||
IncludeEntities *bool `url:"include_entities,omitempty"`
|
||||
TweetMode string `url:"tweet_mode,omitempty"`
|
||||
}
|
||||
|
||||
// Show returns the requested Tweet.
|
||||
// https://dev.twitter.com/rest/reference/get/statuses/show/%3Aid
|
||||
func (s *StatusService) Show(id int64, params *StatusShowParams) (*Tweet, *http.Response, error) {
|
||||
if params == nil {
|
||||
params = &StatusShowParams{}
|
||||
}
|
||||
params.ID = id
|
||||
tweet := new(Tweet)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("show.json").QueryStruct(params).Receive(tweet, apiError)
|
||||
return tweet, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// StatusLookupParams are the parameters for StatusService.Lookup
|
||||
type StatusLookupParams struct {
|
||||
ID []int64 `url:"id,omitempty,comma"`
|
||||
TrimUser *bool `url:"trim_user,omitempty"`
|
||||
IncludeEntities *bool `url:"include_entities,omitempty"`
|
||||
Map *bool `url:"map,omitempty"`
|
||||
TweetMode string `url:"tweet_mode,omitempty"`
|
||||
}
|
||||
|
||||
// Lookup returns the requested Tweets as a slice. Combines ids from the
|
||||
// required ids argument and from params.Id.
|
||||
// https://dev.twitter.com/rest/reference/get/statuses/lookup
|
||||
func (s *StatusService) Lookup(ids []int64, params *StatusLookupParams) ([]Tweet, *http.Response, error) {
|
||||
if params == nil {
|
||||
params = &StatusLookupParams{}
|
||||
}
|
||||
params.ID = append(params.ID, ids...)
|
||||
tweets := new([]Tweet)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("lookup.json").QueryStruct(params).Receive(tweets, apiError)
|
||||
return *tweets, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// StatusUpdateParams are the parameters for StatusService.Update
|
||||
type StatusUpdateParams struct {
|
||||
Status string `url:"status,omitempty"`
|
||||
InReplyToStatusID int64 `url:"in_reply_to_status_id,omitempty"`
|
||||
PossiblySensitive *bool `url:"possibly_sensitive,omitempty"`
|
||||
Lat *float64 `url:"lat,omitempty"`
|
||||
Long *float64 `url:"long,omitempty"`
|
||||
PlaceID string `url:"place_id,omitempty"`
|
||||
DisplayCoordinates *bool `url:"display_coordinates,omitempty"`
|
||||
TrimUser *bool `url:"trim_user,omitempty"`
|
||||
MediaIds []int64 `url:"media_ids,omitempty,comma"`
|
||||
TweetMode string `url:"tweet_mode,omitempty"`
|
||||
}
|
||||
|
||||
// Update updates the user's status, also known as Tweeting.
|
||||
// Requires a user auth context.
|
||||
// https://dev.twitter.com/rest/reference/post/statuses/update
|
||||
func (s *StatusService) Update(status string, params *StatusUpdateParams) (*Tweet, *http.Response, error) {
|
||||
if params == nil {
|
||||
params = &StatusUpdateParams{}
|
||||
}
|
||||
params.Status = status
|
||||
tweet := new(Tweet)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Post("update.json").BodyForm(params).Receive(tweet, apiError)
|
||||
return tweet, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// StatusRetweetParams are the parameters for StatusService.Retweet
|
||||
type StatusRetweetParams struct {
|
||||
ID int64 `url:"id,omitempty"`
|
||||
TrimUser *bool `url:"trim_user,omitempty"`
|
||||
TweetMode string `url:"tweet_mode,omitempty"`
|
||||
}
|
||||
|
||||
// Retweet retweets the Tweet with the given id and returns the original Tweet
|
||||
// with embedded retweet details.
|
||||
// Requires a user auth context.
|
||||
// https://dev.twitter.com/rest/reference/post/statuses/retweet/%3Aid
|
||||
func (s *StatusService) Retweet(id int64, params *StatusRetweetParams) (*Tweet, *http.Response, error) {
|
||||
if params == nil {
|
||||
params = &StatusRetweetParams{}
|
||||
}
|
||||
params.ID = id
|
||||
tweet := new(Tweet)
|
||||
apiError := new(APIError)
|
||||
path := fmt.Sprintf("retweet/%d.json", params.ID)
|
||||
resp, err := s.sling.New().Post(path).BodyForm(params).Receive(tweet, apiError)
|
||||
return tweet, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// StatusUnretweetParams are the parameters for StatusService.Unretweet
|
||||
type StatusUnretweetParams struct {
|
||||
ID int64 `url:"id,omitempty"`
|
||||
TrimUser *bool `url:"trim_user,omitempty"`
|
||||
TweetMode string `url:"tweet_mode,omitempty"`
|
||||
}
|
||||
|
||||
// Unretweet unretweets the Tweet with the given id and returns the original Tweet.
|
||||
// Requires a user auth context.
|
||||
// https://dev.twitter.com/rest/reference/post/statuses/unretweet/%3Aid
|
||||
func (s *StatusService) Unretweet(id int64, params *StatusUnretweetParams) (*Tweet, *http.Response, error) {
|
||||
if params == nil {
|
||||
params = &StatusUnretweetParams{}
|
||||
}
|
||||
params.ID = id
|
||||
tweet := new(Tweet)
|
||||
apiError := new(APIError)
|
||||
path := fmt.Sprintf("unretweet/%d.json", params.ID)
|
||||
resp, err := s.sling.New().Post(path).BodyForm(params).Receive(tweet, apiError)
|
||||
return tweet, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// StatusRetweetsParams are the parameters for StatusService.Retweets
|
||||
type StatusRetweetsParams struct {
|
||||
ID int64 `url:"id,omitempty"`
|
||||
Count int `url:"count,omitempty"`
|
||||
TrimUser *bool `url:"trim_user,omitempty"`
|
||||
TweetMode string `url:"tweet_mode,omitempty"`
|
||||
}
|
||||
|
||||
// Retweets returns the most recent retweets of the Tweet with the given id.
|
||||
// https://dev.twitter.com/rest/reference/get/statuses/retweets/%3Aid
|
||||
func (s *StatusService) Retweets(id int64, params *StatusRetweetsParams) ([]Tweet, *http.Response, error) {
|
||||
if params == nil {
|
||||
params = &StatusRetweetsParams{}
|
||||
}
|
||||
params.ID = id
|
||||
tweets := new([]Tweet)
|
||||
apiError := new(APIError)
|
||||
path := fmt.Sprintf("retweets/%d.json", params.ID)
|
||||
resp, err := s.sling.New().Get(path).QueryStruct(params).Receive(tweets, apiError)
|
||||
return *tweets, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// StatusDestroyParams are the parameters for StatusService.Destroy
|
||||
type StatusDestroyParams struct {
|
||||
ID int64 `url:"id,omitempty"`
|
||||
TrimUser *bool `url:"trim_user,omitempty"`
|
||||
TweetMode string `url:"tweet_mode,omitempty"`
|
||||
}
|
||||
|
||||
// Destroy deletes the Tweet with the given id and returns it if successful.
|
||||
// Requires a user auth context.
|
||||
// https://dev.twitter.com/rest/reference/post/statuses/destroy/%3Aid
|
||||
func (s *StatusService) Destroy(id int64, params *StatusDestroyParams) (*Tweet, *http.Response, error) {
|
||||
if params == nil {
|
||||
params = &StatusDestroyParams{}
|
||||
}
|
||||
params.ID = id
|
||||
tweet := new(Tweet)
|
||||
apiError := new(APIError)
|
||||
path := fmt.Sprintf("destroy/%d.json", params.ID)
|
||||
resp, err := s.sling.New().Post(path).BodyForm(params).Receive(tweet, apiError)
|
||||
return tweet, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// OEmbedTweet represents a Tweet in oEmbed format.
|
||||
type OEmbedTweet struct {
|
||||
URL string `json:"url"`
|
||||
ProviderURL string `json:"provider_url"`
|
||||
ProviderName string `json:"provider_name"`
|
||||
AuthorName string `json:"author_name"`
|
||||
Version string `json:"version"`
|
||||
AuthorURL string `json:"author_url"`
|
||||
Type string `json:"type"`
|
||||
HTML string `json:"html"`
|
||||
Height int64 `json:"height"`
|
||||
Width int64 `json:"width"`
|
||||
CacheAge string `json:"cache_age"`
|
||||
}
|
||||
|
||||
// StatusOEmbedParams are the parameters for StatusService.OEmbed
|
||||
type StatusOEmbedParams struct {
|
||||
ID int64 `url:"id,omitempty"`
|
||||
URL string `url:"url,omitempty"`
|
||||
Align string `url:"align,omitempty"`
|
||||
MaxWidth int64 `url:"maxwidth,omitempty"`
|
||||
HideMedia *bool `url:"hide_media,omitempty"`
|
||||
HideThread *bool `url:"hide_media,omitempty"`
|
||||
OmitScript *bool `url:"hide_media,omitempty"`
|
||||
WidgetType string `url:"widget_type,omitempty"`
|
||||
HideTweet *bool `url:"hide_tweet,omitempty"`
|
||||
}
|
||||
|
||||
// OEmbed returns the requested Tweet in oEmbed format.
|
||||
// https://dev.twitter.com/rest/reference/get/statuses/oembed
|
||||
func (s *StatusService) OEmbed(params *StatusOEmbedParams) (*OEmbedTweet, *http.Response, error) {
|
||||
oEmbedTweet := new(OEmbedTweet)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("oembed.json").QueryStruct(params).Receive(oEmbedTweet, apiError)
|
||||
return oEmbedTweet, resp, relevantError(err, *apiError)
|
||||
}
|
||||
275
vendor/github.com/dghubble/go-twitter/twitter/statuses_test.go
generated
vendored
Normal file
275
vendor/github.com/dghubble/go-twitter/twitter/statuses_test.go
generated
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStatusService_Show(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/show.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"id": "589488862814076930", "include_entities": "false"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"user": {"screen_name": "dghubble"}, "text": ".@audreyr use a DONTREADME file if you really want people to read it :P"}`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &StatusShowParams{ID: 5441, IncludeEntities: Bool(false)}
|
||||
tweet, _, err := client.Statuses.Show(589488862814076930, params)
|
||||
expected := &Tweet{User: &User{ScreenName: "dghubble"}, Text: ".@audreyr use a DONTREADME file if you really want people to read it :P"}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, tweet)
|
||||
}
|
||||
|
||||
func TestStatusService_ShowHandlesNilParams(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/show.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertQuery(t, map[string]string{"id": "589488862814076930"}, r)
|
||||
})
|
||||
client := NewClient(httpClient)
|
||||
client.Statuses.Show(589488862814076930, nil)
|
||||
}
|
||||
|
||||
func TestStatusService_Lookup(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/lookup.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"id": "20,573893817000140800", "trim_user": "true"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[{"id": 20, "text": "just setting up my twttr"}, {"id": 573893817000140800, "text": "Don't get lost #PaxEast2015"}]`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &StatusLookupParams{ID: []int64{20}, TrimUser: Bool(true)}
|
||||
tweets, _, err := client.Statuses.Lookup([]int64{573893817000140800}, params)
|
||||
expected := []Tweet{Tweet{ID: 20, Text: "just setting up my twttr"}, Tweet{ID: 573893817000140800, Text: "Don't get lost #PaxEast2015"}}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, tweets)
|
||||
}
|
||||
|
||||
func TestStatusService_LookupHandlesNilParams(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
mux.HandleFunc("/1.1/statuses/lookup.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertQuery(t, map[string]string{"id": "20,573893817000140800"}, r)
|
||||
})
|
||||
client := NewClient(httpClient)
|
||||
client.Statuses.Lookup([]int64{20, 573893817000140800}, nil)
|
||||
}
|
||||
|
||||
func TestStatusService_Update(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/update.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "POST", r)
|
||||
assertQuery(t, map[string]string{}, r)
|
||||
assertPostForm(t, map[string]string{"status": "very informative tweet", "media_ids": "123456789,987654321", "lat": "37.826706", "long": "-122.42219"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"id": 581980947630845953, "text": "very informative tweet"}`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &StatusUpdateParams{MediaIds: []int64{123456789, 987654321}, Lat: Float(37.826706), Long: Float(-122.422190)}
|
||||
tweet, _, err := client.Statuses.Update("very informative tweet", params)
|
||||
expected := &Tweet{ID: 581980947630845953, Text: "very informative tweet"}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, tweet)
|
||||
}
|
||||
|
||||
func TestStatusService_UpdateHandlesNilParams(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
mux.HandleFunc("/1.1/statuses/update.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertPostForm(t, map[string]string{"status": "very informative tweet"}, r)
|
||||
})
|
||||
client := NewClient(httpClient)
|
||||
client.Statuses.Update("very informative tweet", nil)
|
||||
}
|
||||
|
||||
func TestStatusService_APIError(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
mux.HandleFunc("/1.1/statuses/update.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertPostForm(t, map[string]string{"status": "very informative tweet"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(403)
|
||||
fmt.Fprintf(w, `{"errors": [{"message": "Status is a duplicate", "code": 187}]}`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
_, _, err := client.Statuses.Update("very informative tweet", nil)
|
||||
expected := APIError{
|
||||
Errors: []ErrorDetail{
|
||||
ErrorDetail{Message: "Status is a duplicate", Code: 187},
|
||||
},
|
||||
}
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusService_HTTPError(t *testing.T) {
|
||||
httpClient, _, server := testServer()
|
||||
server.Close()
|
||||
client := NewClient(httpClient)
|
||||
_, _, err := client.Statuses.Update("very informative tweet", nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "connection refused") {
|
||||
t.Errorf("Statuses.Update error expected connection refused, got: \n %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusService_Retweet(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/retweet/20.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "POST", r)
|
||||
assertQuery(t, map[string]string{}, r)
|
||||
assertPostForm(t, map[string]string{"id": "20", "trim_user": "true"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"id": 581980947630202020, "text": "RT @jack: just setting up my twttr", "retweeted_status": {"id": 20, "text": "just setting up my twttr"}}`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &StatusRetweetParams{TrimUser: Bool(true)}
|
||||
tweet, _, err := client.Statuses.Retweet(20, params)
|
||||
expected := &Tweet{ID: 581980947630202020, Text: "RT @jack: just setting up my twttr", RetweetedStatus: &Tweet{ID: 20, Text: "just setting up my twttr"}}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, tweet)
|
||||
}
|
||||
|
||||
func TestStatusService_RetweetHandlesNilParams(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/retweet/20.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertPostForm(t, map[string]string{"id": "20"}, r)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
client.Statuses.Retweet(20, nil)
|
||||
}
|
||||
|
||||
func TestStatusService_Unretweet(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/unretweet/20.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "POST", r)
|
||||
assertQuery(t, map[string]string{}, r)
|
||||
assertPostForm(t, map[string]string{"id": "20", "trim_user": "true"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"id": 581980947630202020, "text":"RT @jack: just setting up my twttr", "retweeted_status": {"id": 20, "text": "just setting up my twttr"}}`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &StatusUnretweetParams{TrimUser: Bool(true)}
|
||||
tweet, _, err := client.Statuses.Unretweet(20, params)
|
||||
expected := &Tweet{ID: 581980947630202020, Text: "RT @jack: just setting up my twttr", RetweetedStatus: &Tweet{ID: 20, Text: "just setting up my twttr"}}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, tweet)
|
||||
}
|
||||
|
||||
func TestStatusService_Retweets(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/retweets/20.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"id": "20", "count": "2"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[{"text": "RT @jack: just setting up my twttr"}, {"text": "RT @jack: just setting up my twttr"}]`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &StatusRetweetsParams{Count: 2}
|
||||
retweets, _, err := client.Statuses.Retweets(20, params)
|
||||
expected := []Tweet{Tweet{Text: "RT @jack: just setting up my twttr"}, Tweet{Text: "RT @jack: just setting up my twttr"}}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, retweets)
|
||||
}
|
||||
|
||||
func TestStatusService_RetweetsHandlesNilParams(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/retweets/20.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertQuery(t, map[string]string{"id": "20"}, r)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
client.Statuses.Retweets(20, nil)
|
||||
}
|
||||
|
||||
func TestStatusService_Destroy(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/destroy/40.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "POST", r)
|
||||
assertQuery(t, map[string]string{}, r)
|
||||
assertPostForm(t, map[string]string{"id": "40", "trim_user": "true"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"id": 40, "text": "wishing I had another sammich"}`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &StatusDestroyParams{TrimUser: Bool(true)}
|
||||
tweet, _, err := client.Statuses.Destroy(40, params)
|
||||
// feed Biz Stone a sammich, he deletes sammich Tweet
|
||||
expected := &Tweet{ID: 40, Text: "wishing I had another sammich"}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, tweet)
|
||||
}
|
||||
|
||||
func TestStatusService_DestroyHandlesNilParams(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/destroy/40.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertPostForm(t, map[string]string{"id": "40"}, r)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
client.Statuses.Destroy(40, nil)
|
||||
}
|
||||
|
||||
func TestStatusService_OEmbed(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/oembed.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"id": "691076766878691329", "maxwidth": "400", "hide_media": "true"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
// abbreviated oEmbed response
|
||||
fmt.Fprintf(w, `{"url": "https://twitter.com/dghubble/statuses/691076766878691329", "width": 400, "html": "<blockquote></blockquote>"}`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
params := &StatusOEmbedParams{
|
||||
ID: 691076766878691329,
|
||||
MaxWidth: 400,
|
||||
HideMedia: Bool(true),
|
||||
}
|
||||
oembed, _, err := client.Statuses.OEmbed(params)
|
||||
expected := &OEmbedTweet{
|
||||
URL: "https://twitter.com/dghubble/statuses/691076766878691329",
|
||||
Width: 400,
|
||||
HTML: "<blockquote></blockquote>",
|
||||
}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, oembed)
|
||||
}
|
||||
110
vendor/github.com/dghubble/go-twitter/twitter/stream_messages.go
generated
vendored
Normal file
110
vendor/github.com/dghubble/go-twitter/twitter/stream_messages.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
package twitter
|
||||
|
||||
// StatusDeletion indicates that a given Tweet has been deleted.
|
||||
// https://dev.twitter.com/streaming/overview/messages-types#status_deletion_notices_delete
|
||||
type StatusDeletion struct {
|
||||
ID int64 `json:"id"`
|
||||
IDStr string `json:"id_str"`
|
||||
UserID int64 `json:"user_id"`
|
||||
UserIDStr string `json:"user_id_str"`
|
||||
}
|
||||
|
||||
type statusDeletionNotice struct {
|
||||
Delete struct {
|
||||
StatusDeletion *StatusDeletion `json:"status"`
|
||||
} `json:"delete"`
|
||||
}
|
||||
|
||||
// LocationDeletion indicates geolocation data must be stripped from a range
|
||||
// of Tweets.
|
||||
// https://dev.twitter.com/streaming/overview/messages-types#Location_deletion_notices_scrub_geo
|
||||
type LocationDeletion struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
UserIDStr string `json:"user_id_str"`
|
||||
UpToStatusID int64 `json:"up_to_status_id"`
|
||||
UpToStatusIDStr string `json:"up_to_status_id_str"`
|
||||
}
|
||||
|
||||
type locationDeletionNotice struct {
|
||||
ScrubGeo *LocationDeletion `json:"scrub_geo"`
|
||||
}
|
||||
|
||||
// StreamLimit indicates a stream matched more statuses than its rate limit
|
||||
// allowed. The track number is the number of undelivered matches.
|
||||
// https://dev.twitter.com/streaming/overview/messages-types#limit_notices
|
||||
type StreamLimit struct {
|
||||
Track int64 `json:"track"`
|
||||
}
|
||||
|
||||
type streamLimitNotice struct {
|
||||
Limit *StreamLimit `json:"limit"`
|
||||
}
|
||||
|
||||
// StatusWithheld indicates a Tweet with the given ID, belonging to UserId,
|
||||
// has been withheld in certain countries.
|
||||
// https://dev.twitter.com/streaming/overview/messages-types#withheld_content_notices
|
||||
type StatusWithheld struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
WithheldInCountries []string `json:"withheld_in_countries"`
|
||||
}
|
||||
|
||||
type statusWithheldNotice struct {
|
||||
StatusWithheld *StatusWithheld `json:"status_withheld"`
|
||||
}
|
||||
|
||||
// UserWithheld indicates a User with the given ID has been withheld in
|
||||
// certain countries.
|
||||
// https://dev.twitter.com/streaming/overview/messages-types#withheld_content_notices
|
||||
type UserWithheld struct {
|
||||
ID int64 `json:"id"`
|
||||
WithheldInCountries []string `json:"withheld_in_countries"`
|
||||
}
|
||||
type userWithheldNotice struct {
|
||||
UserWithheld *UserWithheld `json:"user_withheld"`
|
||||
}
|
||||
|
||||
// StreamDisconnect indicates the stream has been shutdown for some reason.
|
||||
// https://dev.twitter.com/streaming/overview/messages-types#disconnect_messages
|
||||
type StreamDisconnect struct {
|
||||
Code int64 `json:"code"`
|
||||
StreamName string `json:"stream_name"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
type streamDisconnectNotice struct {
|
||||
StreamDisconnect *StreamDisconnect `json:"disconnect"`
|
||||
}
|
||||
|
||||
// StallWarning indicates the client is falling behind in the stream.
|
||||
// https://dev.twitter.com/streaming/overview/messages-types#stall_warnings
|
||||
type StallWarning struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
PercentFull int `json:"percent_full"`
|
||||
}
|
||||
|
||||
type stallWarningNotice struct {
|
||||
StallWarning *StallWarning `json:"warning"`
|
||||
}
|
||||
|
||||
// FriendsList is a list of some of a user's friends.
|
||||
// https://dev.twitter.com/streaming/overview/messages-types#friends_list_friends
|
||||
type FriendsList struct {
|
||||
Friends []int64 `json:"friends"`
|
||||
}
|
||||
|
||||
type directMessageNotice struct {
|
||||
DirectMessage *DirectMessage `json:"direct_message"`
|
||||
}
|
||||
|
||||
// Event is a non-Tweet notification message (e.g. like, retweet, follow).
|
||||
// https://dev.twitter.com/streaming/overview/messages-types#Events_event
|
||||
type Event struct {
|
||||
Event string `json:"event"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Target *User `json:"target"`
|
||||
Source *User `json:"source"`
|
||||
// TODO: add List or deprecate it
|
||||
TargetObject *Tweet `json:"target_object"`
|
||||
}
|
||||
56
vendor/github.com/dghubble/go-twitter/twitter/stream_utils.go
generated
vendored
Normal file
56
vendor/github.com/dghubble/go-twitter/twitter/stream_utils.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// stopped returns true if the done channel receives, false otherwise.
|
||||
func stopped(done <-chan struct{}) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// sleepOrDone pauses the current goroutine until the done channel receives
|
||||
// or until at least the duration d has elapsed, whichever comes first. This
|
||||
// is similar to time.Sleep(d), except it can be interrupted.
|
||||
func sleepOrDone(d time.Duration, done <-chan struct{}) {
|
||||
select {
|
||||
case <-time.After(d):
|
||||
return
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// scanLines is a split function for a Scanner that returns each line of text
|
||||
// stripped of the end-of-line marker "\r\n" used by Twitter Streaming APIs.
|
||||
// This differs from the bufio.ScanLines split function which considers the
|
||||
// '\r' optional.
|
||||
// https://dev.twitter.com/streaming/overview/processing
|
||||
func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if atEOF && len(data) == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
if i := strings.Index(string(data), "\r\n"); i >= 0 {
|
||||
// We have a full '\r\n' terminated line.
|
||||
return i + 2, data[0:i], nil
|
||||
}
|
||||
// If we're at EOF, we have a final, non-terminated line. Return it.
|
||||
if atEOF {
|
||||
return len(data), dropCR(data), nil
|
||||
}
|
||||
// Request more data.
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
func dropCR(data []byte) []byte {
|
||||
if len(data) > 0 && data[len(data)-1] == '\n' {
|
||||
return data[0 : len(data)-1]
|
||||
}
|
||||
return data
|
||||
}
|
||||
64
vendor/github.com/dghubble/go-twitter/twitter/stream_utils_test.go
generated
vendored
Normal file
64
vendor/github.com/dghubble/go-twitter/twitter/stream_utils_test.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStopped(t *testing.T) {
|
||||
done := make(chan struct{})
|
||||
assert.False(t, stopped(done))
|
||||
close(done)
|
||||
assert.True(t, stopped(done))
|
||||
}
|
||||
|
||||
func TestSleepOrDone_Sleep(t *testing.T) {
|
||||
wait := time.Nanosecond * 20
|
||||
done := make(chan struct{})
|
||||
completed := make(chan struct{})
|
||||
go func() {
|
||||
sleepOrDone(wait, done)
|
||||
close(completed)
|
||||
}()
|
||||
// wait for goroutine SleepOrDone to sleep
|
||||
assertDone(t, completed, defaultTestTimeout)
|
||||
}
|
||||
|
||||
func TestSleepOrDone_Done(t *testing.T) {
|
||||
wait := time.Second * 5
|
||||
done := make(chan struct{})
|
||||
completed := make(chan struct{})
|
||||
go func() {
|
||||
sleepOrDone(wait, done)
|
||||
close(completed)
|
||||
}()
|
||||
// close done, interrupting SleepOrDone
|
||||
close(done)
|
||||
// assert that SleepOrDone exited, closing completed
|
||||
assertDone(t, completed, defaultTestTimeout)
|
||||
}
|
||||
|
||||
func TestScanLines(t *testing.T) {
|
||||
cases := []struct {
|
||||
input []byte
|
||||
atEOF bool
|
||||
advance int
|
||||
token []byte
|
||||
}{
|
||||
{[]byte("Line 1\r\n"), false, 8, []byte("Line 1")},
|
||||
{[]byte("Line 1\n"), false, 0, nil},
|
||||
{[]byte("Line 1"), false, 0, nil},
|
||||
{[]byte(""), false, 0, nil},
|
||||
{[]byte("Line 1\r\n"), true, 8, []byte("Line 1")},
|
||||
{[]byte("Line 1\n"), true, 7, []byte("Line 1")},
|
||||
{[]byte("Line 1"), true, 6, []byte("Line 1")},
|
||||
{[]byte(""), true, 0, nil},
|
||||
}
|
||||
for _, c := range cases {
|
||||
advance, token, _ := scanLines(c.input, c.atEOF)
|
||||
assert.Equal(t, c.advance, advance)
|
||||
assert.Equal(t, c.token, token)
|
||||
}
|
||||
}
|
||||
327
vendor/github.com/dghubble/go-twitter/twitter/streams.go
generated
vendored
Normal file
327
vendor/github.com/dghubble/go-twitter/twitter/streams.go
generated
vendored
Normal file
@@ -0,0 +1,327 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/dghubble/sling"
|
||||
)
|
||||
|
||||
const (
|
||||
userAgent = "go-twitter v0.1"
|
||||
publicStream = "https://stream.twitter.com/1.1/"
|
||||
userStream = "https://userstream.twitter.com/1.1/"
|
||||
siteStream = "https://sitestream.twitter.com/1.1/"
|
||||
)
|
||||
|
||||
// StreamService provides methods for accessing the Twitter Streaming API.
|
||||
type StreamService struct {
|
||||
client *http.Client
|
||||
public *sling.Sling
|
||||
user *sling.Sling
|
||||
site *sling.Sling
|
||||
}
|
||||
|
||||
// newStreamService returns a new StreamService.
|
||||
func newStreamService(client *http.Client, sling *sling.Sling) *StreamService {
|
||||
sling.Set("User-Agent", userAgent)
|
||||
return &StreamService{
|
||||
client: client,
|
||||
public: sling.New().Base(publicStream).Path("statuses/"),
|
||||
user: sling.New().Base(userStream),
|
||||
site: sling.New().Base(siteStream),
|
||||
}
|
||||
}
|
||||
|
||||
// StreamFilterParams are parameters for StreamService.Filter.
|
||||
type StreamFilterParams struct {
|
||||
FilterLevel string `url:"filter_level,omitempty"`
|
||||
Follow []string `url:"follow,omitempty,comma"`
|
||||
Language []string `url:"language,omitempty,comma"`
|
||||
Locations []string `url:"locations,omitempty,comma"`
|
||||
StallWarnings *bool `url:"stall_warnings,omitempty"`
|
||||
Track []string `url:"track,omitempty,comma"`
|
||||
}
|
||||
|
||||
// Filter returns messages that match one or more filter predicates.
|
||||
// https://dev.twitter.com/streaming/reference/post/statuses/filter
|
||||
func (srv *StreamService) Filter(params *StreamFilterParams) (*Stream, error) {
|
||||
req, err := srv.public.New().Post("filter.json").QueryStruct(params).Request()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newStream(srv.client, req), nil
|
||||
}
|
||||
|
||||
// StreamSampleParams are the parameters for StreamService.Sample.
|
||||
type StreamSampleParams struct {
|
||||
StallWarnings *bool `url:"stall_warnings,omitempty"`
|
||||
Language []string `url:"language,omitempty,comma"`
|
||||
}
|
||||
|
||||
// Sample returns a small sample of public stream messages.
|
||||
// https://dev.twitter.com/streaming/reference/get/statuses/sample
|
||||
func (srv *StreamService) Sample(params *StreamSampleParams) (*Stream, error) {
|
||||
req, err := srv.public.New().Get("sample.json").QueryStruct(params).Request()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newStream(srv.client, req), nil
|
||||
}
|
||||
|
||||
// StreamUserParams are the parameters for StreamService.User.
|
||||
type StreamUserParams struct {
|
||||
FilterLevel string `url:"filter_level,omitempty"`
|
||||
Language []string `url:"language,omitempty,comma"`
|
||||
Locations []string `url:"locations,omitempty,comma"`
|
||||
Replies string `url:"replies,omitempty"`
|
||||
StallWarnings *bool `url:"stall_warnings,omitempty"`
|
||||
Track []string `url:"track,omitempty,comma"`
|
||||
With string `url:"with,omitempty"`
|
||||
}
|
||||
|
||||
// User returns a stream of messages specific to the authenticated User.
|
||||
// https://dev.twitter.com/streaming/reference/get/user
|
||||
func (srv *StreamService) User(params *StreamUserParams) (*Stream, error) {
|
||||
req, err := srv.user.New().Get("user.json").QueryStruct(params).Request()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newStream(srv.client, req), nil
|
||||
}
|
||||
|
||||
// StreamSiteParams are the parameters for StreamService.Site.
|
||||
type StreamSiteParams struct {
|
||||
FilterLevel string `url:"filter_level,omitempty"`
|
||||
Follow []string `url:"follow,omitempty,comma"`
|
||||
Language []string `url:"language,omitempty,comma"`
|
||||
Replies string `url:"replies,omitempty"`
|
||||
StallWarnings *bool `url:"stall_warnings,omitempty"`
|
||||
With string `url:"with,omitempty"`
|
||||
}
|
||||
|
||||
// Site returns messages for a set of users.
|
||||
// Requires special permission to access.
|
||||
// https://dev.twitter.com/streaming/reference/get/site
|
||||
func (srv *StreamService) Site(params *StreamSiteParams) (*Stream, error) {
|
||||
req, err := srv.site.New().Get("site.json").QueryStruct(params).Request()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newStream(srv.client, req), nil
|
||||
}
|
||||
|
||||
// StreamFirehoseParams are the parameters for StreamService.Firehose.
|
||||
type StreamFirehoseParams struct {
|
||||
Count int `url:"count,omitempty"`
|
||||
FilterLevel string `url:"filter_level,omitempty"`
|
||||
Language []string `url:"language,omitempty,comma"`
|
||||
StallWarnings *bool `url:"stall_warnings,omitempty"`
|
||||
}
|
||||
|
||||
// Firehose returns all public messages and statuses.
|
||||
// Requires special permission to access.
|
||||
// https://dev.twitter.com/streaming/reference/get/statuses/firehose
|
||||
func (srv *StreamService) Firehose(params *StreamFirehoseParams) (*Stream, error) {
|
||||
req, err := srv.public.New().Get("firehose.json").QueryStruct(params).Request()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newStream(srv.client, req), nil
|
||||
}
|
||||
|
||||
// Stream maintains a connection to the Twitter Streaming API, receives
|
||||
// messages from the streaming response, and sends them on the Messages
|
||||
// channel from a goroutine. The stream goroutine stops itself if an EOF is
|
||||
// reached or retry errors occur, also closing the Messages channel.
|
||||
//
|
||||
// The client must Stop() the stream when finished receiving, which will
|
||||
// wait until the stream is properly stopped.
|
||||
type Stream struct {
|
||||
client *http.Client
|
||||
Messages chan interface{}
|
||||
done chan struct{}
|
||||
group *sync.WaitGroup
|
||||
body io.Closer
|
||||
}
|
||||
|
||||
// newStream creates a Stream and starts a goroutine to retry connecting and
|
||||
// receive from a stream response. The goroutine may stop due to retry errors
|
||||
// or be stopped by calling Stop() on the stream.
|
||||
func newStream(client *http.Client, req *http.Request) *Stream {
|
||||
s := &Stream{
|
||||
client: client,
|
||||
Messages: make(chan interface{}),
|
||||
done: make(chan struct{}),
|
||||
group: &sync.WaitGroup{},
|
||||
}
|
||||
s.group.Add(1)
|
||||
go s.retry(req, newExponentialBackOff(), newAggressiveExponentialBackOff())
|
||||
return s
|
||||
}
|
||||
|
||||
// Stop signals retry and receiver to stop, closes the Messages channel, and
|
||||
// blocks until done.
|
||||
func (s *Stream) Stop() {
|
||||
close(s.done)
|
||||
// Scanner does not have a Stop() or take a done channel, so for low volume
|
||||
// streams Scan() blocks until the next keep-alive. Close the resp.Body to
|
||||
// escape and stop the stream in a timely fashion.
|
||||
if s.body != nil {
|
||||
s.body.Close()
|
||||
}
|
||||
// block until the retry goroutine stops
|
||||
s.group.Wait()
|
||||
}
|
||||
|
||||
// retry retries making the given http.Request and receiving the response
|
||||
// according to the Twitter backoff policies. Callers should invoke in a
|
||||
// goroutine since backoffs sleep between retries.
|
||||
// https://dev.twitter.com/streaming/overview/connecting
|
||||
func (s *Stream) retry(req *http.Request, expBackOff backoff.BackOff, aggExpBackOff backoff.BackOff) {
|
||||
// close Messages channel and decrement the wait group counter
|
||||
defer close(s.Messages)
|
||||
defer s.group.Done()
|
||||
|
||||
var wait time.Duration
|
||||
for !stopped(s.done) {
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
// stop retrying for HTTP protocol errors
|
||||
s.Messages <- err
|
||||
return
|
||||
}
|
||||
// when err is nil, resp contains a non-nil Body which must be closed
|
||||
defer resp.Body.Close()
|
||||
s.body = resp.Body
|
||||
switch resp.StatusCode {
|
||||
case 200:
|
||||
// receive stream response Body, handles closing
|
||||
s.receive(resp.Body)
|
||||
expBackOff.Reset()
|
||||
aggExpBackOff.Reset()
|
||||
case 503:
|
||||
// exponential backoff
|
||||
wait = expBackOff.NextBackOff()
|
||||
case 420, 429:
|
||||
// aggressive exponential backoff
|
||||
wait = aggExpBackOff.NextBackOff()
|
||||
default:
|
||||
// stop retrying for other response codes
|
||||
resp.Body.Close()
|
||||
return
|
||||
}
|
||||
// close response before each retry
|
||||
resp.Body.Close()
|
||||
if wait == backoff.Stop {
|
||||
return
|
||||
}
|
||||
sleepOrDone(wait, s.done)
|
||||
}
|
||||
}
|
||||
|
||||
// receive scans a stream response body, JSON decodes tokens to messages, and
|
||||
// sends messages to the Messages channel. Receiving continues until an EOF,
|
||||
// scan error, or the done channel is closed.
|
||||
func (s *Stream) receive(body io.ReadCloser) {
|
||||
defer body.Close()
|
||||
// A bufio.Scanner steps through 'tokens' of data on each Scan() using a
|
||||
// SplitFunc. SplitFunc tokenizes input bytes to return the number of bytes
|
||||
// to advance, the token slice of bytes, and any errors.
|
||||
scanner := bufio.NewScanner(body)
|
||||
// default ScanLines SplitFunc is incorrect for Twitter Streams, set custom
|
||||
scanner.Split(scanLines)
|
||||
for !stopped(s.done) && scanner.Scan() {
|
||||
token := scanner.Bytes()
|
||||
if len(token) == 0 {
|
||||
// empty keep-alive
|
||||
continue
|
||||
}
|
||||
select {
|
||||
// send messages, data, or errors
|
||||
case s.Messages <- getMessage(token):
|
||||
continue
|
||||
// allow client to Stop(), even if not receiving
|
||||
case <-s.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getMessage unmarshals the token and returns a message struct, if the type
|
||||
// can be determined. Otherwise, returns the token unmarshalled into a data
|
||||
// map[string]interface{} or the unmarshal error.
|
||||
func getMessage(token []byte) interface{} {
|
||||
var data map[string]interface{}
|
||||
// unmarshal JSON encoded token into a map for
|
||||
err := json.Unmarshal(token, &data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return decodeMessage(token, data)
|
||||
}
|
||||
|
||||
// decodeMessage determines the message type from known data keys, allocates
|
||||
// at most one message struct, and JSON decodes the token into the message.
|
||||
// Returns the message struct or the data map if the message type could not be
|
||||
// determined.
|
||||
func decodeMessage(token []byte, data map[string]interface{}) interface{} {
|
||||
if hasPath(data, "retweet_count") {
|
||||
tweet := new(Tweet)
|
||||
json.Unmarshal(token, tweet)
|
||||
return tweet
|
||||
} else if hasPath(data, "direct_message") {
|
||||
notice := new(directMessageNotice)
|
||||
json.Unmarshal(token, notice)
|
||||
return notice.DirectMessage
|
||||
} else if hasPath(data, "delete") {
|
||||
notice := new(statusDeletionNotice)
|
||||
json.Unmarshal(token, notice)
|
||||
return notice.Delete.StatusDeletion
|
||||
} else if hasPath(data, "scrub_geo") {
|
||||
notice := new(locationDeletionNotice)
|
||||
json.Unmarshal(token, notice)
|
||||
return notice.ScrubGeo
|
||||
} else if hasPath(data, "limit") {
|
||||
notice := new(streamLimitNotice)
|
||||
json.Unmarshal(token, notice)
|
||||
return notice.Limit
|
||||
} else if hasPath(data, "status_withheld") {
|
||||
notice := new(statusWithheldNotice)
|
||||
json.Unmarshal(token, notice)
|
||||
return notice.StatusWithheld
|
||||
} else if hasPath(data, "user_withheld") {
|
||||
notice := new(userWithheldNotice)
|
||||
json.Unmarshal(token, notice)
|
||||
return notice.UserWithheld
|
||||
} else if hasPath(data, "disconnect") {
|
||||
notice := new(streamDisconnectNotice)
|
||||
json.Unmarshal(token, notice)
|
||||
return notice.StreamDisconnect
|
||||
} else if hasPath(data, "warning") {
|
||||
notice := new(stallWarningNotice)
|
||||
json.Unmarshal(token, notice)
|
||||
return notice.StallWarning
|
||||
} else if hasPath(data, "friends") {
|
||||
friendsList := new(FriendsList)
|
||||
json.Unmarshal(token, friendsList)
|
||||
return friendsList
|
||||
} else if hasPath(data, "event") {
|
||||
event := new(Event)
|
||||
json.Unmarshal(token, event)
|
||||
return event
|
||||
}
|
||||
// message type unknown, return the data map[string]interface{}
|
||||
return data
|
||||
}
|
||||
|
||||
// hasPath returns true if the map contains the given key, false otherwise.
|
||||
func hasPath(data map[string]interface{}, key string) bool {
|
||||
_, ok := data[key]
|
||||
return ok
|
||||
}
|
||||
352
vendor/github.com/dghubble/go-twitter/twitter/streams_test.go
generated
vendored
Normal file
352
vendor/github.com/dghubble/go-twitter/twitter/streams_test.go
generated
vendored
Normal file
@@ -0,0 +1,352 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStream_MessageJSONError(t *testing.T) {
|
||||
badJSON := []byte(`{`)
|
||||
msg := getMessage(badJSON)
|
||||
assert.EqualError(t, msg.(error), "unexpected end of JSON input")
|
||||
}
|
||||
|
||||
func TestStream_GetMessageTweet(t *testing.T) {
|
||||
msgJSON := []byte(`{"id": 20, "text": "just setting up my twttr", "retweet_count": "68535"}`)
|
||||
msg := getMessage(msgJSON)
|
||||
assert.IsType(t, &Tweet{}, msg)
|
||||
}
|
||||
|
||||
func TestStream_GetMessageDirectMessage(t *testing.T) {
|
||||
msgJSON := []byte(`{"direct_message": {"id": 666024290140217347}}`)
|
||||
msg := getMessage(msgJSON)
|
||||
assert.IsType(t, &DirectMessage{}, msg)
|
||||
}
|
||||
|
||||
func TestStream_GetMessageDelete(t *testing.T) {
|
||||
msgJSON := []byte(`{"delete": { "id": 20}}`)
|
||||
msg := getMessage(msgJSON)
|
||||
assert.IsType(t, &StatusDeletion{}, msg)
|
||||
}
|
||||
|
||||
func TestStream_GetMessageLocationDeletion(t *testing.T) {
|
||||
msgJSON := []byte(`{"scrub_geo": { "up_to_status_id": 20}}`)
|
||||
msg := getMessage(msgJSON)
|
||||
assert.IsType(t, &LocationDeletion{}, msg)
|
||||
}
|
||||
|
||||
func TestStream_GetMessageStreamLimit(t *testing.T) {
|
||||
msgJSON := []byte(`{"limit": { "track": 10 }}`)
|
||||
msg := getMessage(msgJSON)
|
||||
assert.IsType(t, &StreamLimit{}, msg)
|
||||
}
|
||||
|
||||
func TestStream_StatusWithheld(t *testing.T) {
|
||||
msgJSON := []byte(`{"status_withheld": { "id": 20, "user_id": 12, "withheld_in_countries":["USA", "China"] }}`)
|
||||
msg := getMessage(msgJSON)
|
||||
assert.IsType(t, &StatusWithheld{}, msg)
|
||||
}
|
||||
|
||||
func TestStream_UserWithheld(t *testing.T) {
|
||||
msgJSON := []byte(`{"user_withheld": { "id": 12, "withheld_in_countries":["USA", "China"] }}`)
|
||||
msg := getMessage(msgJSON)
|
||||
assert.IsType(t, &UserWithheld{}, msg)
|
||||
}
|
||||
|
||||
func TestStream_StreamDisconnect(t *testing.T) {
|
||||
msgJSON := []byte(`{"disconnect": { "code": "420", "stream_name": "streaming stuff", "reason": "too many connections" }}`)
|
||||
msg := getMessage(msgJSON)
|
||||
assert.IsType(t, &StreamDisconnect{}, msg)
|
||||
}
|
||||
|
||||
func TestStream_StallWarning(t *testing.T) {
|
||||
msgJSON := []byte(`{"warning": { "code": "420", "percent_full": 90, "message": "a lot of messages" }}`)
|
||||
msg := getMessage(msgJSON)
|
||||
assert.IsType(t, &StallWarning{}, msg)
|
||||
}
|
||||
|
||||
func TestStream_FriendsList(t *testing.T) {
|
||||
msgJSON := []byte(`{"friends": [666024290140217347, 666024290140217349, 666024290140217342]}`)
|
||||
msg := getMessage(msgJSON)
|
||||
assert.IsType(t, &FriendsList{}, msg)
|
||||
}
|
||||
|
||||
func TestStream_Event(t *testing.T) {
|
||||
msgJSON := []byte(`{"event": "block", "target": {"name": "XKCD Comic", "favourites_count": 2}, "source": {"name": "XKCD Comic2", "favourites_count": 3}, "created_at": "Sat Sep 4 16:10:54 +0000 2010"}`)
|
||||
msg := getMessage(msgJSON)
|
||||
assert.IsType(t, &Event{}, msg)
|
||||
}
|
||||
|
||||
func TestStream_Unknown(t *testing.T) {
|
||||
msgJSON := []byte(`{"unknown_data": {"new_twitter_type":"unexpected"}}`)
|
||||
msg := getMessage(msgJSON)
|
||||
assert.IsType(t, map[string]interface{}{}, msg)
|
||||
}
|
||||
|
||||
func TestStream_Filter(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
reqCount := 0
|
||||
mux.HandleFunc("/1.1/statuses/filter.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "POST", r)
|
||||
assertQuery(t, map[string]string{"track": "gophercon,golang"}, r)
|
||||
switch reqCount {
|
||||
case 0:
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
fmt.Fprintf(w,
|
||||
`{"text": "Gophercon talks!"}`+"\r\n"+
|
||||
`{"text": "Gophercon super talks!"}`+"\r\n",
|
||||
)
|
||||
default:
|
||||
// Only allow first request
|
||||
http.Error(w, "Stream API not available!", 130)
|
||||
}
|
||||
reqCount++
|
||||
})
|
||||
|
||||
counts := &counter{}
|
||||
demux := newCounterDemux(counts)
|
||||
client := NewClient(httpClient)
|
||||
streamFilterParams := &StreamFilterParams{
|
||||
Track: []string{"gophercon", "golang"},
|
||||
}
|
||||
stream, err := client.Streams.Filter(streamFilterParams)
|
||||
// assert that the expected messages are received
|
||||
assert.NoError(t, err)
|
||||
defer stream.Stop()
|
||||
for message := range stream.Messages {
|
||||
demux.Handle(message)
|
||||
}
|
||||
expectedCounts := &counter{all: 2, other: 2}
|
||||
assert.Equal(t, expectedCounts, counts)
|
||||
}
|
||||
|
||||
func TestStream_Sample(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
reqCount := 0
|
||||
mux.HandleFunc("/1.1/statuses/sample.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"stall_warnings": "true"}, r)
|
||||
switch reqCount {
|
||||
case 0:
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
fmt.Fprintf(w,
|
||||
`{"text": "Gophercon talks!"}`+"\r\n"+
|
||||
`{"text": "Gophercon super talks!"}`+"\r\n",
|
||||
)
|
||||
default:
|
||||
// Only allow first request
|
||||
http.Error(w, "Stream API not available!", 130)
|
||||
}
|
||||
reqCount++
|
||||
})
|
||||
|
||||
counts := &counter{}
|
||||
demux := newCounterDemux(counts)
|
||||
client := NewClient(httpClient)
|
||||
streamSampleParams := &StreamSampleParams{
|
||||
StallWarnings: Bool(true),
|
||||
}
|
||||
stream, err := client.Streams.Sample(streamSampleParams)
|
||||
// assert that the expected messages are received
|
||||
assert.NoError(t, err)
|
||||
defer stream.Stop()
|
||||
for message := range stream.Messages {
|
||||
demux.Handle(message)
|
||||
}
|
||||
expectedCounts := &counter{all: 2, other: 2}
|
||||
assert.Equal(t, expectedCounts, counts)
|
||||
}
|
||||
|
||||
func TestStream_User(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
reqCount := 0
|
||||
mux.HandleFunc("/1.1/user.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"stall_warnings": "true", "with": "followings"}, r)
|
||||
switch reqCount {
|
||||
case 0:
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
fmt.Fprintf(w, `{"friends": [666024290140217347, 666024290140217349, 666024290140217342]}`+"\r\n"+"\r\n")
|
||||
default:
|
||||
// Only allow first request
|
||||
http.Error(w, "Stream API not available!", 130)
|
||||
}
|
||||
reqCount++
|
||||
})
|
||||
|
||||
counts := &counter{}
|
||||
demux := newCounterDemux(counts)
|
||||
client := NewClient(httpClient)
|
||||
streamUserParams := &StreamUserParams{
|
||||
StallWarnings: Bool(true),
|
||||
With: "followings",
|
||||
}
|
||||
stream, err := client.Streams.User(streamUserParams)
|
||||
// assert that the expected messages are received
|
||||
assert.NoError(t, err)
|
||||
defer stream.Stop()
|
||||
for message := range stream.Messages {
|
||||
demux.Handle(message)
|
||||
}
|
||||
expectedCounts := &counter{all: 1, friendsList: 1}
|
||||
assert.Equal(t, expectedCounts, counts)
|
||||
}
|
||||
|
||||
func TestStream_Site(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
reqCount := 0
|
||||
mux.HandleFunc("/1.1/site.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"follow": "666024290140217347,666024290140217349"}, r)
|
||||
switch reqCount {
|
||||
case 0:
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
fmt.Fprintf(w,
|
||||
`{"text": "Gophercon talks!"}`+"\r\n"+
|
||||
`{"text": "Gophercon super talks!"}`+"\r\n",
|
||||
)
|
||||
default:
|
||||
// Only allow first request
|
||||
http.Error(w, "Stream API not available!", 130)
|
||||
}
|
||||
reqCount++
|
||||
})
|
||||
|
||||
counts := &counter{}
|
||||
demux := newCounterDemux(counts)
|
||||
client := NewClient(httpClient)
|
||||
streamSiteParams := &StreamSiteParams{
|
||||
Follow: []string{"666024290140217347", "666024290140217349"},
|
||||
}
|
||||
stream, err := client.Streams.Site(streamSiteParams)
|
||||
// assert that the expected messages are received
|
||||
assert.NoError(t, err)
|
||||
defer stream.Stop()
|
||||
for message := range stream.Messages {
|
||||
demux.Handle(message)
|
||||
}
|
||||
expectedCounts := &counter{all: 2, other: 2}
|
||||
assert.Equal(t, expectedCounts, counts)
|
||||
}
|
||||
|
||||
func TestStream_PublicFirehose(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
reqCount := 0
|
||||
mux.HandleFunc("/1.1/statuses/firehose.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"count": "100"}, r)
|
||||
switch reqCount {
|
||||
case 0:
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
fmt.Fprintf(w,
|
||||
`{"text": "Gophercon talks!"}`+"\r\n"+
|
||||
`{"text": "Gophercon super talks!"}`+"\r\n",
|
||||
)
|
||||
default:
|
||||
// Only allow first request
|
||||
http.Error(w, "Stream API not available!", 130)
|
||||
}
|
||||
reqCount++
|
||||
})
|
||||
|
||||
counts := &counter{}
|
||||
demux := newCounterDemux(counts)
|
||||
client := NewClient(httpClient)
|
||||
streamFirehoseParams := &StreamFirehoseParams{
|
||||
Count: 100,
|
||||
}
|
||||
stream, err := client.Streams.Firehose(streamFirehoseParams)
|
||||
// assert that the expected messages are received
|
||||
assert.NoError(t, err)
|
||||
defer stream.Stop()
|
||||
for message := range stream.Messages {
|
||||
demux.Handle(message)
|
||||
}
|
||||
expectedCounts := &counter{all: 2, other: 2}
|
||||
assert.Equal(t, expectedCounts, counts)
|
||||
}
|
||||
|
||||
func TestStreamRetry_ExponentialBackoff(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
reqCount := 0
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch reqCount {
|
||||
case 0:
|
||||
http.Error(w, "Service Unavailable", 503)
|
||||
default:
|
||||
// Only allow first request
|
||||
http.Error(w, "Stream API not available!", 130)
|
||||
}
|
||||
reqCount++
|
||||
})
|
||||
stream := &Stream{
|
||||
client: httpClient,
|
||||
Messages: make(chan interface{}),
|
||||
done: make(chan struct{}),
|
||||
group: &sync.WaitGroup{},
|
||||
}
|
||||
stream.group.Add(1)
|
||||
req, _ := http.NewRequest("GET", "http://example.com/", nil)
|
||||
expBackoff := &BackOffRecorder{}
|
||||
// receive messages and throw them away
|
||||
go NewSwitchDemux().HandleChan(stream.Messages)
|
||||
stream.retry(req, expBackoff, nil)
|
||||
defer stream.Stop()
|
||||
// assert exponential backoff in response to 503
|
||||
assert.Equal(t, 1, expBackoff.Count)
|
||||
}
|
||||
|
||||
func TestStreamRetry_AggressiveBackoff(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
reqCount := 0
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch reqCount {
|
||||
case 0:
|
||||
http.Error(w, "Enhance Your Calm", 420)
|
||||
case 1:
|
||||
http.Error(w, "Too Many Requests", 429)
|
||||
default:
|
||||
// Only allow first request
|
||||
http.Error(w, "Stream API not available!", 130)
|
||||
}
|
||||
reqCount++
|
||||
})
|
||||
stream := &Stream{
|
||||
client: httpClient,
|
||||
Messages: make(chan interface{}),
|
||||
done: make(chan struct{}),
|
||||
group: &sync.WaitGroup{},
|
||||
}
|
||||
stream.group.Add(1)
|
||||
req, _ := http.NewRequest("GET", "http://example.com/", nil)
|
||||
aggExpBackoff := &BackOffRecorder{}
|
||||
// receive messages and throw them away
|
||||
go NewSwitchDemux().HandleChan(stream.Messages)
|
||||
stream.retry(req, nil, aggExpBackoff)
|
||||
defer stream.Stop()
|
||||
// assert aggressive exponential backoff in response to 420 and 429
|
||||
assert.Equal(t, 2, aggExpBackoff.Count)
|
||||
}
|
||||
109
vendor/github.com/dghubble/go-twitter/twitter/timelines.go
generated
vendored
Normal file
109
vendor/github.com/dghubble/go-twitter/twitter/timelines.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dghubble/sling"
|
||||
)
|
||||
|
||||
// TimelineService provides methods for accessing Twitter status timeline
|
||||
// API endpoints.
|
||||
type TimelineService struct {
|
||||
sling *sling.Sling
|
||||
}
|
||||
|
||||
// newTimelineService returns a new TimelineService.
|
||||
func newTimelineService(sling *sling.Sling) *TimelineService {
|
||||
return &TimelineService{
|
||||
sling: sling.Path("statuses/"),
|
||||
}
|
||||
}
|
||||
|
||||
// UserTimelineParams are the parameters for TimelineService.UserTimeline.
|
||||
type UserTimelineParams struct {
|
||||
UserID int64 `url:"user_id,omitempty"`
|
||||
ScreenName string `url:"screen_name,omitempty"`
|
||||
Count int `url:"count,omitempty"`
|
||||
SinceID int64 `url:"since_id,omitempty"`
|
||||
MaxID int64 `url:"max_id,omitempty"`
|
||||
TrimUser *bool `url:"trim_user,omitempty"`
|
||||
ExcludeReplies *bool `url:"exclude_replies,omitempty"`
|
||||
IncludeRetweets *bool `url:"include_rts,omitempty"`
|
||||
TweetMode string `url:"tweet_mode,omitempty"`
|
||||
}
|
||||
|
||||
// UserTimeline returns recent Tweets from the specified user.
|
||||
// https://dev.twitter.com/rest/reference/get/statuses/user_timeline
|
||||
func (s *TimelineService) UserTimeline(params *UserTimelineParams) ([]Tweet, *http.Response, error) {
|
||||
tweets := new([]Tweet)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("user_timeline.json").QueryStruct(params).Receive(tweets, apiError)
|
||||
return *tweets, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// HomeTimelineParams are the parameters for TimelineService.HomeTimeline.
|
||||
type HomeTimelineParams struct {
|
||||
Count int `url:"count,omitempty"`
|
||||
SinceID int64 `url:"since_id,omitempty"`
|
||||
MaxID int64 `url:"max_id,omitempty"`
|
||||
TrimUser *bool `url:"trim_user,omitempty"`
|
||||
ExcludeReplies *bool `url:"exclude_replies,omitempty"`
|
||||
ContributorDetails *bool `url:"contributor_details,omitempty"`
|
||||
IncludeEntities *bool `url:"include_entities,omitempty"`
|
||||
TweetMode string `url:"tweet_mode,omitempty"`
|
||||
}
|
||||
|
||||
// HomeTimeline returns recent Tweets and retweets from the user and those
|
||||
// users they follow.
|
||||
// Requires a user auth context.
|
||||
// https://dev.twitter.com/rest/reference/get/statuses/home_timeline
|
||||
func (s *TimelineService) HomeTimeline(params *HomeTimelineParams) ([]Tweet, *http.Response, error) {
|
||||
tweets := new([]Tweet)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("home_timeline.json").QueryStruct(params).Receive(tweets, apiError)
|
||||
return *tweets, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// MentionTimelineParams are the parameters for TimelineService.MentionTimeline.
|
||||
type MentionTimelineParams struct {
|
||||
Count int `url:"count,omitempty"`
|
||||
SinceID int64 `url:"since_id,omitempty"`
|
||||
MaxID int64 `url:"max_id,omitempty"`
|
||||
TrimUser *bool `url:"trim_user,omitempty"`
|
||||
ContributorDetails *bool `url:"contributor_details,omitempty"`
|
||||
IncludeEntities *bool `url:"include_entities,omitempty"`
|
||||
TweetMode string `url:"tweet_mode,omitempty"`
|
||||
}
|
||||
|
||||
// MentionTimeline returns recent Tweet mentions of the authenticated user.
|
||||
// Requires a user auth context.
|
||||
// https://dev.twitter.com/rest/reference/get/statuses/mentions_timeline
|
||||
func (s *TimelineService) MentionTimeline(params *MentionTimelineParams) ([]Tweet, *http.Response, error) {
|
||||
tweets := new([]Tweet)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("mentions_timeline.json").QueryStruct(params).Receive(tweets, apiError)
|
||||
return *tweets, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// RetweetsOfMeTimelineParams are the parameters for
|
||||
// TimelineService.RetweetsOfMeTimeline.
|
||||
type RetweetsOfMeTimelineParams struct {
|
||||
Count int `url:"count,omitempty"`
|
||||
SinceID int64 `url:"since_id,omitempty"`
|
||||
MaxID int64 `url:"max_id,omitempty"`
|
||||
TrimUser *bool `url:"trim_user,omitempty"`
|
||||
IncludeEntities *bool `url:"include_entities,omitempty"`
|
||||
IncludeUserEntities *bool `url:"include_user_entities"`
|
||||
TweetMode string `url:"tweet_mode,omitempty"`
|
||||
}
|
||||
|
||||
// RetweetsOfMeTimeline returns the most recent Tweets by the authenticated
|
||||
// user that have been retweeted by others.
|
||||
// Requires a user auth context.
|
||||
// https://dev.twitter.com/rest/reference/get/statuses/retweets_of_me
|
||||
func (s *TimelineService) RetweetsOfMeTimeline(params *RetweetsOfMeTimelineParams) ([]Tweet, *http.Response, error) {
|
||||
tweets := new([]Tweet)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("retweets_of_me.json").QueryStruct(params).Receive(tweets, apiError)
|
||||
return *tweets, resp, relevantError(err, *apiError)
|
||||
}
|
||||
81
vendor/github.com/dghubble/go-twitter/twitter/timelines_test.go
generated
vendored
Normal file
81
vendor/github.com/dghubble/go-twitter/twitter/timelines_test.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTimelineService_UserTimeline(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/user_timeline.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"user_id": "113419064", "trim_user": "true", "include_rts": "false"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[{"text": "Gophercon talks!"}, {"text": "Why gophers are so adorable"}]`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
tweets, _, err := client.Timelines.UserTimeline(&UserTimelineParams{UserID: 113419064, TrimUser: Bool(true), IncludeRetweets: Bool(false)})
|
||||
expected := []Tweet{Tweet{Text: "Gophercon talks!"}, Tweet{Text: "Why gophers are so adorable"}}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, tweets)
|
||||
}
|
||||
|
||||
func TestTimelineService_HomeTimeline(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/home_timeline.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"since_id": "589147592367431680", "exclude_replies": "false"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[{"text": "Live on #Periscope"}, {"text": "Clickbait journalism"}, {"text": "Useful announcement"}]`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
tweets, _, err := client.Timelines.HomeTimeline(&HomeTimelineParams{SinceID: 589147592367431680, ExcludeReplies: Bool(false)})
|
||||
expected := []Tweet{Tweet{Text: "Live on #Periscope"}, Tweet{Text: "Clickbait journalism"}, Tweet{Text: "Useful announcement"}}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, tweets)
|
||||
}
|
||||
|
||||
func TestTimelineService_MentionTimeline(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/mentions_timeline.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"count": "20", "include_entities": "false"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[{"text": "@dghubble can I get verified?"}, {"text": "@dghubble why are gophers so great?"}]`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
tweets, _, err := client.Timelines.MentionTimeline(&MentionTimelineParams{Count: 20, IncludeEntities: Bool(false)})
|
||||
expected := []Tweet{Tweet{Text: "@dghubble can I get verified?"}, Tweet{Text: "@dghubble why are gophers so great?"}}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, tweets)
|
||||
}
|
||||
|
||||
func TestTimelineService_RetweetsOfMeTimeline(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/statuses/retweets_of_me.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"trim_user": "false", "include_user_entities": "false"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[{"text": "RT Twitter UK edition"}, {"text": "RT Triply-replicated Gophers"}]`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
tweets, _, err := client.Timelines.RetweetsOfMeTimeline(&RetweetsOfMeTimelineParams{TrimUser: Bool(false), IncludeUserEntities: Bool(false)})
|
||||
expected := []Tweet{Tweet{Text: "RT Twitter UK edition"}, Tweet{Text: "RT Triply-replicated Gophers"}}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, tweets)
|
||||
}
|
||||
102
vendor/github.com/dghubble/go-twitter/twitter/trends.go
generated
vendored
Normal file
102
vendor/github.com/dghubble/go-twitter/twitter/trends.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dghubble/sling"
|
||||
)
|
||||
|
||||
// TrendsService provides methods for accessing Twitter trends API endpoints.
|
||||
type TrendsService struct {
|
||||
sling *sling.Sling
|
||||
}
|
||||
|
||||
// newTrendsService returns a new TrendsService.
|
||||
func newTrendsService(sling *sling.Sling) *TrendsService {
|
||||
return &TrendsService{
|
||||
sling: sling.Path("trends/"),
|
||||
}
|
||||
}
|
||||
|
||||
// PlaceType represents a twitter trends PlaceType.
|
||||
type PlaceType struct {
|
||||
Code int `json:"code"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Location reporesents a twitter Location.
|
||||
type Location struct {
|
||||
Country string `json:"country"`
|
||||
CountryCode string `json:"countryCode"`
|
||||
Name string `json:"name"`
|
||||
ParentID int `json:"parentid"`
|
||||
PlaceType PlaceType `json:"placeType"`
|
||||
URL string `json:"url"`
|
||||
WOEID int64 `json:"woeid"`
|
||||
}
|
||||
|
||||
// Available returns the locations that Twitter has trending topic information for.
|
||||
// https://dev.twitter.com/rest/reference/get/trends/available
|
||||
func (s *TrendsService) Available() ([]Location, *http.Response, error) {
|
||||
locations := new([]Location)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("available.json").Receive(locations, apiError)
|
||||
return *locations, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// Trend represents a twitter trend.
|
||||
type Trend struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
PromotedContent string `json:"promoted_content"`
|
||||
Query string `json:"query"`
|
||||
TweetVolume int64 `json:"tweet_volume"`
|
||||
}
|
||||
|
||||
// TrendsList represents a list of twitter trends.
|
||||
type TrendsList struct {
|
||||
Trends []Trend `json:"trends"`
|
||||
AsOf string `json:"as_of"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Locations []TrendsLocation `json:"locations"`
|
||||
}
|
||||
|
||||
// TrendsLocation represents a twitter trend location.
|
||||
type TrendsLocation struct {
|
||||
Name string `json:"name"`
|
||||
WOEID int64 `json:"woeid"`
|
||||
}
|
||||
|
||||
// TrendsPlaceParams are the parameters for Trends.Place.
|
||||
type TrendsPlaceParams struct {
|
||||
WOEID int64 `url:"id,omitempty"`
|
||||
Exclude string `url:"exclude,omitempty"`
|
||||
}
|
||||
|
||||
// Place returns the top 50 trending topics for a specific WOEID.
|
||||
// https://dev.twitter.com/rest/reference/get/trends/place
|
||||
func (s *TrendsService) Place(woeid int64, params *TrendsPlaceParams) ([]TrendsList, *http.Response, error) {
|
||||
if params == nil {
|
||||
params = &TrendsPlaceParams{}
|
||||
}
|
||||
trendsList := new([]TrendsList)
|
||||
params.WOEID = woeid
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("place.json").QueryStruct(params).Receive(trendsList, apiError)
|
||||
return *trendsList, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// ClosestParams are the parameters for Trends.Closest.
|
||||
type ClosestParams struct {
|
||||
Lat float64 `url:"lat"`
|
||||
Long float64 `url:"long"`
|
||||
}
|
||||
|
||||
// Closest returns the locations that Twitter has trending topic information for, closest to a specified location.
|
||||
// https://dev.twitter.com/rest/reference/get/trends/closest
|
||||
func (s *TrendsService) Closest(params *ClosestParams) ([]Location, *http.Response, error) {
|
||||
locations := new([]Location)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("closest.json").QueryStruct(params).Receive(locations, apiError)
|
||||
return *locations, resp, relevantError(err, *apiError)
|
||||
}
|
||||
87
vendor/github.com/dghubble/go-twitter/twitter/trends_test.go
generated
vendored
Normal file
87
vendor/github.com/dghubble/go-twitter/twitter/trends_test.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTrendsService_Available(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/trends/available.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[{"country": "Sweden","countryCode": "SE","name": "Sweden","parentid": 1,"placeType": {"code": 12,"name": "Country"},"url": "http://where.yahooapis.com/v1/place/23424954","woeid": 23424954}]`)
|
||||
})
|
||||
expected := []Location{
|
||||
Location{
|
||||
Country: "Sweden",
|
||||
CountryCode: "SE",
|
||||
Name: "Sweden",
|
||||
ParentID: 1,
|
||||
PlaceType: PlaceType{Code: 12, Name: "Country"},
|
||||
URL: "http://where.yahooapis.com/v1/place/23424954",
|
||||
WOEID: 23424954,
|
||||
},
|
||||
}
|
||||
|
||||
client := NewClient(httpClient)
|
||||
locations, _, err := client.Trends.Available()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, locations)
|
||||
}
|
||||
|
||||
func TestTrendsService_Place(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/trends/place.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"id": "123456"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[{"trends":[{"name":"#gotwitter"}], "as_of": "2017-02-08T16:18:18Z", "created_at": "2017-02-08T16:10:33Z","locations":[{"name": "Worldwide","woeid": 1}]}]`)
|
||||
})
|
||||
expected := []TrendsList{TrendsList{
|
||||
Trends: []Trend{Trend{Name: "#gotwitter"}},
|
||||
AsOf: "2017-02-08T16:18:18Z",
|
||||
CreatedAt: "2017-02-08T16:10:33Z",
|
||||
Locations: []TrendsLocation{TrendsLocation{Name: "Worldwide", WOEID: 1}},
|
||||
}}
|
||||
|
||||
client := NewClient(httpClient)
|
||||
places, _, err := client.Trends.Place(123456, &TrendsPlaceParams{})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, places)
|
||||
}
|
||||
|
||||
func TestTrendsService_Closest(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/trends/closest.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"lat": "37.781157", "long": "-122.400612831116"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[{"country": "Sweden","countryCode": "SE","name": "Sweden","parentid": 1,"placeType": {"code": 12,"name": "Country"},"url": "http://where.yahooapis.com/v1/place/23424954","woeid": 23424954}]`)
|
||||
})
|
||||
expected := []Location{
|
||||
Location{
|
||||
Country: "Sweden",
|
||||
CountryCode: "SE",
|
||||
Name: "Sweden",
|
||||
ParentID: 1,
|
||||
PlaceType: PlaceType{Code: 12, Name: "Country"},
|
||||
URL: "http://where.yahooapis.com/v1/place/23424954",
|
||||
WOEID: 23424954,
|
||||
},
|
||||
}
|
||||
|
||||
client := NewClient(httpClient)
|
||||
locations, _, err := client.Trends.Closest(&ClosestParams{Lat: 37.781157, Long: -122.400612831116})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, locations)
|
||||
}
|
||||
61
vendor/github.com/dghubble/go-twitter/twitter/twitter.go
generated
vendored
Normal file
61
vendor/github.com/dghubble/go-twitter/twitter/twitter.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dghubble/sling"
|
||||
)
|
||||
|
||||
const twitterAPI = "https://api.twitter.com/1.1/"
|
||||
|
||||
// Client is a Twitter client for making Twitter API requests.
|
||||
type Client struct {
|
||||
sling *sling.Sling
|
||||
// Twitter API Services
|
||||
Accounts *AccountService
|
||||
DirectMessages *DirectMessageService
|
||||
Favorites *FavoriteService
|
||||
Followers *FollowerService
|
||||
Friends *FriendService
|
||||
Friendships *FriendshipService
|
||||
Search *SearchService
|
||||
Statuses *StatusService
|
||||
Streams *StreamService
|
||||
Timelines *TimelineService
|
||||
Trends *TrendsService
|
||||
Users *UserService
|
||||
}
|
||||
|
||||
// NewClient returns a new Client.
|
||||
func NewClient(httpClient *http.Client) *Client {
|
||||
base := sling.New().Client(httpClient).Base(twitterAPI)
|
||||
return &Client{
|
||||
sling: base,
|
||||
Accounts: newAccountService(base.New()),
|
||||
DirectMessages: newDirectMessageService(base.New()),
|
||||
Favorites: newFavoriteService(base.New()),
|
||||
Followers: newFollowerService(base.New()),
|
||||
Friends: newFriendService(base.New()),
|
||||
Friendships: newFriendshipService(base.New()),
|
||||
Search: newSearchService(base.New()),
|
||||
Statuses: newStatusService(base.New()),
|
||||
Streams: newStreamService(httpClient, base.New()),
|
||||
Timelines: newTimelineService(base.New()),
|
||||
Trends: newTrendsService(base.New()),
|
||||
Users: newUserService(base.New()),
|
||||
}
|
||||
}
|
||||
|
||||
// Bool returns a new pointer to the given bool value.
|
||||
func Bool(v bool) *bool {
|
||||
ptr := new(bool)
|
||||
*ptr = v
|
||||
return ptr
|
||||
}
|
||||
|
||||
// Float returns a new pointer to the given float64 value.
|
||||
func Float(v float64) *float64 {
|
||||
ptr := new(float64)
|
||||
*ptr = v
|
||||
return ptr
|
||||
}
|
||||
93
vendor/github.com/dghubble/go-twitter/twitter/twitter_test.go
generated
vendored
Normal file
93
vendor/github.com/dghubble/go-twitter/twitter/twitter_test.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var defaultTestTimeout = time.Second * 1
|
||||
|
||||
// testServer returns an http Client, ServeMux, and Server. The client proxies
|
||||
// requests to the server and handlers can be registered on the mux to handle
|
||||
// requests. The caller must close the test server.
|
||||
func testServer() (*http.Client, *http.ServeMux, *httptest.Server) {
|
||||
mux := http.NewServeMux()
|
||||
server := httptest.NewServer(mux)
|
||||
transport := &RewriteTransport{&http.Transport{
|
||||
Proxy: func(req *http.Request) (*url.URL, error) {
|
||||
return url.Parse(server.URL)
|
||||
},
|
||||
}}
|
||||
client := &http.Client{Transport: transport}
|
||||
return client, mux, server
|
||||
}
|
||||
|
||||
// RewriteTransport rewrites https requests to http to avoid TLS cert issues
|
||||
// during testing.
|
||||
type RewriteTransport struct {
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
// RoundTrip rewrites the request scheme to http and calls through to the
|
||||
// composed RoundTripper or if it is nil, to the http.DefaultTransport.
|
||||
func (t *RewriteTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req.URL.Scheme = "http"
|
||||
if t.Transport == nil {
|
||||
return http.DefaultTransport.RoundTrip(req)
|
||||
}
|
||||
return t.Transport.RoundTrip(req)
|
||||
}
|
||||
|
||||
func assertMethod(t *testing.T, expectedMethod string, req *http.Request) {
|
||||
assert.Equal(t, expectedMethod, req.Method)
|
||||
}
|
||||
|
||||
// assertQuery tests that the Request has the expected url query key/val pairs
|
||||
func assertQuery(t *testing.T, expected map[string]string, req *http.Request) {
|
||||
queryValues := req.URL.Query()
|
||||
expectedValues := url.Values{}
|
||||
for key, value := range expected {
|
||||
expectedValues.Add(key, value)
|
||||
}
|
||||
assert.Equal(t, expectedValues, queryValues)
|
||||
}
|
||||
|
||||
// assertPostForm tests that the Request has the expected key values pairs url
|
||||
// encoded in its Body
|
||||
func assertPostForm(t *testing.T, expected map[string]string, req *http.Request) {
|
||||
req.ParseForm() // parses request Body to put url.Values in r.Form/r.PostForm
|
||||
expectedValues := url.Values{}
|
||||
for key, value := range expected {
|
||||
expectedValues.Add(key, value)
|
||||
}
|
||||
assert.Equal(t, expectedValues, req.Form)
|
||||
}
|
||||
|
||||
// assertDone asserts that the empty struct channel is closed before the given
|
||||
// timeout elapses.
|
||||
func assertDone(t *testing.T, ch <-chan struct{}, timeout time.Duration) {
|
||||
select {
|
||||
case <-ch:
|
||||
_, more := <-ch
|
||||
assert.False(t, more)
|
||||
case <-time.After(timeout):
|
||||
t.Errorf("expected channel to be closed within timeout %v", timeout)
|
||||
}
|
||||
}
|
||||
|
||||
// assertClosed asserts that the channel is closed before the given timeout
|
||||
// elapses.
|
||||
func assertClosed(t *testing.T, ch <-chan interface{}, timeout time.Duration) {
|
||||
select {
|
||||
case <-ch:
|
||||
_, more := <-ch
|
||||
assert.False(t, more)
|
||||
case <-time.After(timeout):
|
||||
t.Errorf("expected channel to be closed within timeout %v", timeout)
|
||||
}
|
||||
}
|
||||
122
vendor/github.com/dghubble/go-twitter/twitter/users.go
generated
vendored
Normal file
122
vendor/github.com/dghubble/go-twitter/twitter/users.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dghubble/sling"
|
||||
)
|
||||
|
||||
// User represents a Twitter User.
|
||||
// https://dev.twitter.com/overview/api/users
|
||||
type User struct {
|
||||
ContributorsEnabled bool `json:"contributors_enabled"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
DefaultProfile bool `json:"default_profile"`
|
||||
DefaultProfileImage bool `json:"default_profile_image"`
|
||||
Description string `json:"description"`
|
||||
Email string `json:"email"`
|
||||
Entities *UserEntities `json:"entities"`
|
||||
FavouritesCount int `json:"favourites_count"`
|
||||
FollowRequestSent bool `json:"follow_request_sent"`
|
||||
Following bool `json:"following"`
|
||||
FollowersCount int `json:"followers_count"`
|
||||
FriendsCount int `json:"friends_count"`
|
||||
GeoEnabled bool `json:"geo_enabled"`
|
||||
ID int64 `json:"id"`
|
||||
IDStr string `json:"id_str"`
|
||||
IsTranslator bool `json:"is_translator"`
|
||||
Lang string `json:"lang"`
|
||||
ListedCount int `json:"listed_count"`
|
||||
Location string `json:"location"`
|
||||
Name string `json:"name"`
|
||||
Notifications bool `json:"notifications"`
|
||||
ProfileBackgroundColor string `json:"profile_background_color"`
|
||||
ProfileBackgroundImageURL string `json:"profile_background_image_url"`
|
||||
ProfileBackgroundImageURLHttps string `json:"profile_background_image_url_https"`
|
||||
ProfileBackgroundTile bool `json:"profile_background_tile"`
|
||||
ProfileBannerURL string `json:"profile_banner_url"`
|
||||
ProfileImageURL string `json:"profile_image_url"`
|
||||
ProfileImageURLHttps string `json:"profile_image_url_https"`
|
||||
ProfileLinkColor string `json:"profile_link_color"`
|
||||
ProfileSidebarBorderColor string `json:"profile_sidebar_border_color"`
|
||||
ProfileSidebarFillColor string `json:"profile_sidebar_fill_color"`
|
||||
ProfileTextColor string `json:"profile_text_color"`
|
||||
ProfileUseBackgroundImage bool `json:"profile_use_background_image"`
|
||||
Protected bool `json:"protected"`
|
||||
ScreenName string `json:"screen_name"`
|
||||
ShowAllInlineMedia bool `json:"show_all_inline_media"`
|
||||
Status *Tweet `json:"status"`
|
||||
StatusesCount int `json:"statuses_count"`
|
||||
Timezone string `json:"time_zone"`
|
||||
URL string `json:"url"`
|
||||
UtcOffset int `json:"utc_offset"`
|
||||
Verified bool `json:"verified"`
|
||||
WithheldInCountries []string `json:"withheld_in_countries"`
|
||||
WithholdScope string `json:"withheld_scope"`
|
||||
}
|
||||
|
||||
// UserService provides methods for accessing Twitter user API endpoints.
|
||||
type UserService struct {
|
||||
sling *sling.Sling
|
||||
}
|
||||
|
||||
// newUserService returns a new UserService.
|
||||
func newUserService(sling *sling.Sling) *UserService {
|
||||
return &UserService{
|
||||
sling: sling.Path("users/"),
|
||||
}
|
||||
}
|
||||
|
||||
// UserShowParams are the parameters for UserService.Show.
|
||||
type UserShowParams struct {
|
||||
UserID int64 `url:"user_id,omitempty"`
|
||||
ScreenName string `url:"screen_name,omitempty"`
|
||||
IncludeEntities *bool `url:"include_entities,omitempty"` // whether 'status' should include entities
|
||||
}
|
||||
|
||||
// Show returns the requested User.
|
||||
// https://dev.twitter.com/rest/reference/get/users/show
|
||||
func (s *UserService) Show(params *UserShowParams) (*User, *http.Response, error) {
|
||||
user := new(User)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("show.json").QueryStruct(params).Receive(user, apiError)
|
||||
return user, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// UserLookupParams are the parameters for UserService.Lookup.
|
||||
type UserLookupParams struct {
|
||||
UserID []int64 `url:"user_id,omitempty,comma"`
|
||||
ScreenName []string `url:"screen_name,omitempty,comma"`
|
||||
IncludeEntities *bool `url:"include_entities,omitempty"` // whether 'status' should include entities
|
||||
}
|
||||
|
||||
// Lookup returns the requested Users as a slice.
|
||||
// https://dev.twitter.com/rest/reference/get/users/lookup
|
||||
func (s *UserService) Lookup(params *UserLookupParams) ([]User, *http.Response, error) {
|
||||
users := new([]User)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("lookup.json").QueryStruct(params).Receive(users, apiError)
|
||||
return *users, resp, relevantError(err, *apiError)
|
||||
}
|
||||
|
||||
// UserSearchParams are the parameters for UserService.Search.
|
||||
type UserSearchParams struct {
|
||||
Query string `url:"q,omitempty"`
|
||||
Page int `url:"page,omitempty"` // 1-based page number
|
||||
Count int `url:"count,omitempty"`
|
||||
IncludeEntities *bool `url:"include_entities,omitempty"` // whether 'status' should include entities
|
||||
}
|
||||
|
||||
// Search queries public user accounts.
|
||||
// Requires a user auth context.
|
||||
// https://dev.twitter.com/rest/reference/get/users/search
|
||||
func (s *UserService) Search(query string, params *UserSearchParams) ([]User, *http.Response, error) {
|
||||
if params == nil {
|
||||
params = &UserSearchParams{}
|
||||
}
|
||||
params.Query = query
|
||||
users := new([]User)
|
||||
apiError := new(APIError)
|
||||
resp, err := s.sling.New().Get("search.json").QueryStruct(params).Receive(users, apiError)
|
||||
return *users, resp, relevantError(err, *apiError)
|
||||
}
|
||||
92
vendor/github.com/dghubble/go-twitter/twitter/users_test.go
generated
vendored
Normal file
92
vendor/github.com/dghubble/go-twitter/twitter/users_test.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
package twitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUserService_Show(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/users/show.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"screen_name": "xkcdComic"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"name": "XKCD Comic", "favourites_count": 2}`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
user, _, err := client.Users.Show(&UserShowParams{ScreenName: "xkcdComic"})
|
||||
expected := &User{Name: "XKCD Comic", FavouritesCount: 2}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, user)
|
||||
}
|
||||
|
||||
func TestUserService_LookupWithIds(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/users/lookup.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"user_id": "113419064,623265148"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[{"screen_name": "golang"}, {"screen_name": "dghubble"}]`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
users, _, err := client.Users.Lookup(&UserLookupParams{UserID: []int64{113419064, 623265148}})
|
||||
expected := []User{User{ScreenName: "golang"}, User{ScreenName: "dghubble"}}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, users)
|
||||
}
|
||||
|
||||
func TestUserService_LookupWithScreenNames(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/users/lookup.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"screen_name": "foo,bar"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[{"name": "Foo"}, {"name": "Bar"}]`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
users, _, err := client.Users.Lookup(&UserLookupParams{ScreenName: []string{"foo", "bar"}})
|
||||
expected := []User{User{Name: "Foo"}, User{Name: "Bar"}}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, users)
|
||||
}
|
||||
|
||||
func TestUserService_Search(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/users/search.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertMethod(t, "GET", r)
|
||||
assertQuery(t, map[string]string{"count": "11", "q": "news"}, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `[{"name": "BBC"}, {"name": "BBC Breaking News"}]`)
|
||||
})
|
||||
|
||||
client := NewClient(httpClient)
|
||||
users, _, err := client.Users.Search("news", &UserSearchParams{Query: "override me", Count: 11})
|
||||
expected := []User{User{Name: "BBC"}, User{Name: "BBC Breaking News"}}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, users)
|
||||
}
|
||||
|
||||
func TestUserService_SearchHandlesNilParams(t *testing.T) {
|
||||
httpClient, mux, server := testServer()
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/1.1/users/search.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
assertQuery(t, map[string]string{"q": "news"}, r)
|
||||
})
|
||||
client := NewClient(httpClient)
|
||||
client.Users.Search("news", nil)
|
||||
}
|
||||
Reference in New Issue
Block a user