Skip to content
This repository was archived by the owner on Nov 5, 2022. It is now read-only.

Commit 04b1953

Browse files
authored
Merge pull request #31 from Fallenstedt/rc/0.4.0
0.4.0
2 parents 58dba3d + 08b887d commit 04b1953

18 files changed

+579
-293
lines changed

README.md

Lines changed: 133 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,25 @@ See [examples](https://github.com/fallenstedt/twitter-stream/tree/master/example
1818
`go get github.com/fallenstedt/twitter-stream`
1919

2020

21+
## Projects Using TwitterStream
22+
Below are projects using this library! Add yours below with a pull request.
23+
24+
* [FindTechJobs](https://www.findtechjobs.io/) - Search latest tech job postings from around the world
25+
26+
2127

2228

2329
## Examples
30+
See [examples](https://github.com/fallenstedt/twitter-stream/tree/master/example), or follow the guide below.
2431

2532
#### Starting a stream
2633

2734
##### Obtain an Access Token using your Twitter Access Key and Secret.
28-
You need an access token to do any streaming. `twitterstream` provides an easy way to fetch an access token.
35+
You need an access token to do any streaming. `twitterstream` provides an easy way to fetch an access token. Use your
36+
access token and secret access token from twitter to request a bearer token.
37+
2938
```go
3039
tok, err := twitterstream.NewTokenGenerator().SetApiKeyAndSecret("key", "secret").RequestBearerToken()
31-
32-
if err != nil {
33-
panic(err)
34-
}
3540
```
3641

3742
##### Create a streaming api
@@ -41,135 +46,151 @@ Create a twitterstream instance with your access token from above.
4146
api := twitterstream.NewTwitterStream(tok.AccessToken)
4247
```
4348

44-
##### Set your unmarshal hook
45-
It is encouraged you set an unmarshal hook for thread-safety. Go's `bytes.Buffer` is not thread safe. Sharing a `bytes.Buffer`
46-
across multiple goroutines introduces risk of panics when decoding json [source](https://github.com/Fallenstedt/twitter-stream/issues/13).
47-
To avoid panics, it's encouraged to unmarshal json in the same goroutine where the `bytes.Buffer` exists. Use `SetUnmarshalHook` to set a function that unmarshals json.
49+
##### Create rules
4850

49-
By default, twitterstream's unmarshal hook will return `[]byte` if you want to live dangerously.
51+
We need to create [twitter streaming rules](https://developer.twitter.com/en/docs/twitter-api/tweets/filtered-stream/integrate/build-a-rule) so we can get tweets that we want.
52+
The filtered stream endpoints deliver filtered Tweets to you in real-time that match on a set of rules that are applied to the stream. Rules are made up of operators that are used to match on a variety of Tweet attributes.
53+
Below we create three rules. One for puppy tweets with images, another for cat tweets with images, and the other of unique English golang job postings. Each rule is
54+
associated with their own tag.
5055

5156
```go
52-
api.Stream.SetUnmarshalHook(func(bytes []byte) (interface{}, error) {
53-
// StreemData is a struct that represents your returned json
54-
// This is a quick resource to generate a struct from your json
55-
// https://mholt.github.io/json-to-go/
56-
data := StreamData{}
57-
if err := json.Unmarshal(bytes, &data); err != nil {
58-
log.Printf("Failed to unmarshal bytes: %v", err)
59-
}
60-
return data, err
61-
})
62-
```
6357

58+
rules := twitterstream.NewRuleBuilder().
59+
AddRule("cat has:images", "cat tweets with images").
60+
AddRule("puppy has:images", "puppy tweets with images").
61+
AddRule("lang:en -is:retweet -is:quote (#golangjobs OR #gojobs)", "golang jobs").
62+
Build()
6463

64+
// Create will create twitter rules
65+
// dryRun is set to false. Set to true to test out your request
66+
res, err := api.Rules.Create(rules, false)
6567

68+
// Get will get your current rules
69+
res, err := api.Rules.Get()
6670

67-
##### Start Stream
68-
Start your stream. This is a long-running HTTP GET request.
69-
You can get specific data you want by adding [query params](https://developer.twitter.com/en/docs/twitter-api/tweets/filtered-stream/api-reference/get-tweets-search-stream).
70-
Additionally, [view an example of query params here](https://developer.twitter.com/en/docs/twitter-api/expansions), or in the [examples](https://github.com/fallenstedt/twitter-stream/tree/master/example)
71+
// Delete will delete your rules by their id
72+
// dryRun is set to false. Set to true to test out your request
73+
res, err := api.Rules.Delete(rules.NewDeleteRulesRequest(1468427075727945728, 1468427075727945729), false)
7174

72-
```go
73-
err := api.Stream.StartStream("?expansions=author_id&tweet.fields=created_at")
7475

75-
if err != nil {
76-
panic(err)
77-
}
7876
```
7977

80-
Consume Messages from the Stream
81-
Handle any `io.EOF` and other errors that arise first, then unmarshal your bytes into your favorite struct. Below is an example with strings
82-
```go
83-
go func() {
84-
for message := range api.Stream.GetMessages() {
85-
if message.Err != nil {
86-
panic(message.Err)
87-
}
88-
// Will print something like:
89-
//{"data":{"id":"1356479201000","text":"Look at this cat picture"},"matching_rules":[{"id":12345,"tag":"cat tweets with images"}]}
90-
fmt.Println(string(message.Data))
91-
}
92-
}()
93-
94-
time.Sleep(time.Second * 30)
95-
api.Stream.StopStream()
96-
```
97-
98-
#### Creating, Deleting, and Getting Rules
99-
100-
##### Obtain an Access Token using your Twitter Access Key and Secret.
101-
You need an access token to do anything. `twitterstream` provides an easy way to fetch an access token.
102-
```go
103-
tok, err := twitterstream.NewTokenGenerator().SetApiKeyAndSecret("key", "secret").RequestBearerToken()
104-
105-
if err != nil {
106-
panic(err)
107-
}
108-
```
10978

110-
##### Create a streaming api
111-
Create a twitterstream instance with your access token from above.
79+
##### Set your unmarshal hook
80+
It is encouraged you set an unmarshal hook for thread-safety. Go's `bytes.Buffer` is not thread safe. Sharing a `bytes.Buffer`
81+
across multiple goroutines introduces risk of panics when decoding json.
82+
To avoid panics, it's encouraged to unmarshal json in the same goroutine where the `bytes.Buffer` exists. Use `SetUnmarshalHook` to set a function that unmarshals json.
11283

113-
```go
114-
api := twitterstream.NewTwitterStream(tok.AccessToken)
115-
```
84+
By default, twitterstream's unmarshal hook will return `[]byte` if you want to live dangerously.
11685

117-
##### Get Rules
118-
Use the `Rules` struct to access different Rules endpoints as defined in [Twitter's API Reference](https://developer.twitter.com/en/docs/twitter-api/tweets/filtered-stream/api-reference)
11986
```go
120-
res, err := api.Rules.GetRules()
121-
122-
if err != nil {
123-
panic(err)
124-
}
12587

126-
if res.Errors != nil && len(res.Errors) > 0 {
127-
//https://developer.twitter.com/en/support/twitter-api/error-troubleshooting
128-
panic(fmt.Sprintf("Received an error from twitter: %v", res.Errors))
129-
}
88+
type StreamDataExample struct {
89+
Data struct {
90+
Text string `json:"text"`
91+
ID string `json:"id"`
92+
CreatedAt time.Time `json:"created_at"`
93+
AuthorID string `json:"author_id"`
94+
} `json:"data"`
95+
Includes struct {
96+
Users []struct {
97+
ID string `json:"id"`
98+
Name string `json:"name"`
99+
Username string `json:"username"`
100+
} `json:"users"`
101+
} `json:"includes"`
102+
MatchingRules []struct {
103+
ID string `json:"id"`
104+
Tag string `json:"tag"`
105+
} `json:"matching_rules"`
106+
}
107+
108+
api.SetUnmarshalHook(func(bytes []byte) (interface{}, error) {
109+
data := StreamDataExample{}
110+
111+
if err := json.Unmarshal(bytes, &data); err != nil {
112+
fmt.Printf("failed to unmarshal bytes: %v", err)
113+
}
114+
115+
return data, err
116+
})
117+
```
130118

131-
fmt.Println(res.Data)
132-
```
119+
##### Start Stream
120+
Start your stream. This is a long-running HTTP GET request.
121+
You can get specific data you want by adding [query params](https://developer.twitter.com/en/docs/twitter-api/tweets/filtered-stream/api-reference/get-tweets-search-stream).
122+
Additionally, [view an example of query params here](https://developer.twitter.com/en/docs/twitter-api/expansions), or in the [examples](https://github.com/fallenstedt/twitter-stream/tree/master/example)
133123

134-
##### Add Rules
135-
```go
136-
res, err := api.Rules.AddRules(`{
137-
"add": [
138-
{"value": "cat has:images", "tag": "cat tweets with images"}
139-
]
140-
}`, true) // dryRun is set to true
141-
142-
if err != nil {
143-
panic(err)
144-
}
145-
146-
if res.Errors != nil && len(res.Errors) > 0 {
147-
//https://developer.twitter.com/en/support/twitter-api/error-troubleshooting
148-
panic(fmt.Sprintf("Received an error from twitter: %v", res.Errors))
149-
}
150-
```
151-
##### Delete Rules
152124
```go
153-
// use api.Rules.GetRules to find the ID number for an existing rule
154-
res, err := api.Rules.AddRules(`{
155-
"delete": {
156-
"ids": ["1234567890"]
157-
}
158-
}`, true)
159-
160-
if err != nil {
161-
panic(err)
162-
}
163-
164-
if res.Errors != nil && len(res.Errors) > 0 {
165-
//https://developer.twitter.com/en/support/twitter-api/error-troubleshooting
166-
panic(fmt.Sprintf("Received an error from twitter: %v", res.Errors))
167-
}
168125

126+
// Steps from above, Placed into a single function
127+
// This assumes you have at least one streaming rule configured.
128+
// returns a configured instance of twitterstream
129+
func fetchTweets() stream.IStream {
130+
tok, err := twitterstream.NewTokenGenerator().SetApiKeyAndSecret(KEY, SECRET).RequestBearerToken()
131+
132+
if err != nil {
133+
panic(err)
134+
}
135+
136+
api := twitterstream.NewTwitterStream(tok).Stream
137+
api.SetUnmarshalHook(func(bytes []byte) (interface{}, error) {
138+
data := StreamDataExample{}
139+
140+
if err := json.Unmarshal(bytes, &data); err != nil {
141+
fmt.Printf("failed to unmarshal bytes: %v", err)
142+
}
143+
return data, err
144+
})
145+
err = api.StartStream("?expansions=author_id&tweet.fields=created_at")
146+
147+
if err != nil {
148+
panic(err)
149+
}
150+
151+
return api
152+
}
153+
154+
// This will run forever
155+
func initiateStream() {
156+
fmt.Println("Starting Stream")
157+
158+
// Start the stream
159+
// And return the library's api
160+
api := fetchTweets()
161+
162+
// When the loop below ends, restart the stream
163+
defer initiateStream()
164+
165+
// Start processing data from twitter
166+
for tweet := range api.GetMessages() {
167+
168+
// Handle disconnections from twitter
169+
// https://developer.twitter.com/en/docs/twitter-api/tweets/volume-streams/integrate/handling-disconnections
170+
if tweet.Err != nil {
171+
fmt.Printf("got error from twitter: %v", tweet.Err)
172+
173+
// Notice we "StopStream" and then "continue" the loop instead of breaking.
174+
// StopStream will close the long running GET request to Twitter's v2 Streaming endpoint by
175+
// closing the `GetMessages` channel. Once it's closed, it's safe to perform a new network request
176+
// with `StartStream`
177+
api.StopStream()
178+
continue
179+
}
180+
result := tweet.Data.(StreamDataExample)
181+
182+
// Here I am printing out the text.
183+
// You can send this off to a queue for processing.
184+
// Or do your processing here in the loop
185+
fmt.Println(result.Data.Text)
186+
}
187+
188+
fmt.Println("Stopped Stream")
189+
}
169190
```
170191

171-
172192
## Contributing
173193

174-
Pull requests are always welcome. Please accompany a pull request with tests.
194+
Pull requests and feature requests are always welcome.
195+
Please accompany a pull request with tests.
175196

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.3.3
1+
0.4.0

0 commit comments

Comments
 (0)