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

Commit

Permalink
Merge pull request #29 from Fallenstedt/story/23
Browse files Browse the repository at this point in the history
Story/23
  • Loading branch information
Fallenstedt authored Nov 25, 2021
2 parents f930d3c + 6b7c9ba commit f43d02a
Show file tree
Hide file tree
Showing 9 changed files with 43 additions and 69 deletions.
29 changes: 3 additions & 26 deletions example/create_rules_example.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,15 @@ package main
import (
"fmt"
twitterstream "github.com/fallenstedt/twitter-stream"
"time"
)

const key = "YOUR_KEY"
const secret = "YOUR_SECRET"

func main() {

addRules()
getRules()
// You can delete the rules created in this example
//deleteRules()
}

type StreamData struct {
Data struct {
Text string `json:"text"`
ID string `json:"id"`
CreatedAt time.Time `json:"created_at"`
AuthorID string `json:"author_id"`
} `json:"data"`
Includes struct {
Users []struct {
ID string `json:"id"`
Name string `json:"name"`
Username string `json:"username"`
} `json:"users"`
} `json:"includes"`
MatchingRules []struct {
ID string `json:"id"`
Tag string `json:"tag"`
} `json:"matching_rules"`
addRules()
getRules()
deleteRules()
}

func addRules() {
Expand Down
2 changes: 1 addition & 1 deletion httpclient/httpclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type (
)

// NewHttpClient constructs a an HttpClient to interact with twitter.
func NewHttpClient(token string) *httpClient {
func NewHttpClient(token string) IHttpClient {
Endpoints["rules"] = "https://api.twitter.com/2/tweets/search/stream/rules"
Endpoints["stream"] = "https://api.twitter.com/2/tweets/search/stream"
Endpoints["token"] = "https://api.twitter.com/oauth2/token"
Expand Down
2 changes: 1 addition & 1 deletion rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type (
}
)

func newRules(httpClient httpclient.IHttpClient) *rules {
func newRules(httpClient httpclient.IHttpClient) IRules {
return &rules{httpClient: httpClient}
}

Expand Down
2 changes: 1 addition & 1 deletion stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type (
}
)

func newStream(httpClient httpclient.IHttpClient, reader IStreamResponseBodyReader) *Stream {
func newStream(httpClient httpclient.IHttpClient, reader IStreamResponseBodyReader) IStream {
return &Stream{
unmarshalHook: func(bytes []byte) (interface{}, error) {
return bytes, nil
Expand Down
10 changes: 9 additions & 1 deletion stream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ func TestGetMessages(t *testing.T) {
func TestStopStream(t *testing.T) {
client := httpclient.NewHttpClientMock("foobar")
reader := newStreamResponseBodyReader()
instance := newStream(client, reader)
instance := &Stream{
unmarshalHook: func(bytes []byte) (interface{}, error) {
return bytes, nil
},
messages: make(chan StreamMessage),
done: make(chan struct{}),
reader: reader,
httpClient: client,
}

instance.StopStream()
result := <-instance.done
Expand Down
17 changes: 1 addition & 16 deletions stream_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"bufio"
"bytes"
"io"
"time"
)

// stopped returns true if the done channel receives, false otherwise.
Expand All @@ -22,20 +21,6 @@ func stopped(done <-chan struct{}) bool {
}
}

// 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{}) {
sleep := time.NewTimer(d)
defer sleep.Stop()
select {
case <-sleep.C:
return
case <-done:
return
}
}

type (
// IStreamResponseBodyReader is the interface the streamResponseBodyReader implements.
IStreamResponseBodyReader interface {
Expand All @@ -53,7 +38,7 @@ type (

// newStreamResponseBodyReader returns an instance of streamResponseBodyReader
// for the given Twitter stream response body.
func newStreamResponseBodyReader() *streamResponseBodyReader {
func newStreamResponseBodyReader() IStreamResponseBodyReader {
return &streamResponseBodyReader{}
}

Expand Down
27 changes: 15 additions & 12 deletions token_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,35 @@ import (
)

type (
//ITokenGenerator is the interface that tokenGenerator implements.
//ITokenGenerator is the interface that TokenGenerator implements.
ITokenGenerator interface {
RequestBearerToken() *requestBearerTokenResponse
SetApiKeyAndSecret(apiKey, apiSecret string) *tokenGenerator
RequestBearerToken() (*RequestBearerTokenResponse, error)
SetApiKeyAndSecret(apiKey, apiSecret string) ITokenGenerator
}
tokenGenerator struct {
TokenGenerator struct {
httpClient httpclient.IHttpClient
apiKey string
apiSecret string
}
requestBearerTokenResponse struct {
RequestBearerTokenResponse struct {
TokenType string `json:"token_type"`
AccessToken string `json:"access_token"`
}
)

func newTokenGenerator(httpClient httpclient.IHttpClient) *tokenGenerator {
return &tokenGenerator{httpClient: httpClient}
func newTokenGenerator(httpClient httpclient.IHttpClient) ITokenGenerator {
return &TokenGenerator{httpClient: httpClient}
}

// SetApiKeyAndSecret sets the apiKey and apiSecret fields for the tokenGenerator instance.
func (a *tokenGenerator) SetApiKeyAndSecret(apiKey, apiSecret string) *tokenGenerator {
// SetApiKeyAndSecret sets the apiKey and apiSecret fields for the TokenGenerator instance.
func (a *TokenGenerator) SetApiKeyAndSecret(apiKey, apiSecret string) ITokenGenerator {
a.apiKey = apiKey
a.apiSecret = apiSecret
return a
}

// RequestBearerToken requests a bearer token from twitter using the apiKey and apiSecret.
func (a *tokenGenerator) RequestBearerToken() (*requestBearerTokenResponse, error) {
func (a *TokenGenerator) RequestBearerToken() (*RequestBearerTokenResponse, error) {

resp, err := a.httpClient.NewHttpRequest(&httpclient.RequestOpts{
Headers: []struct {
Expand All @@ -55,12 +55,15 @@ func (a *tokenGenerator) RequestBearerToken() (*requestBearerTokenResponse, erro
}

defer resp.Body.Close()
data := new(requestBearerTokenResponse)
data := new(RequestBearerTokenResponse)
json.NewDecoder(resp.Body).Decode(data)

return data, nil
}

func (a *tokenGenerator) base64EncodeKeys() string {

func (a *TokenGenerator) base64EncodeKeys() string {
// See Step 1 of encoding consumer key and secret twitter application-only requests here
// https://developer.twitter.com/en/docs/authentication/oauth-2-0/application-only
return base64.StdEncoding.EncodeToString([]byte(a.apiKey + ":" + a.apiSecret))
}
12 changes: 6 additions & 6 deletions token_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ func TestSetApiKeyAndSecret(t *testing.T) {
var tests = []struct {
apiKey string
apiSecret string
result tokenGenerator
result TokenGenerator
}{
{"foo", "bar", tokenGenerator{apiKey: "foo", apiSecret: "bar"}},
{"", "", tokenGenerator{apiKey: "", apiSecret: ""}},
{"foo", "bar", TokenGenerator{apiKey: "foo", apiSecret: "bar"}},
{"", "", TokenGenerator{apiKey: "", apiSecret: ""}},
}

for i, tt := range tests {
testName := fmt.Sprintf("(%d) %s %s", i, tt.apiKey, tt.apiSecret)
t.Run(testName, func(t *testing.T) {
result := newTokenGenerator(httpclient.NewHttpClientMock(""))
result := &TokenGenerator{httpClient: httpclient.NewHttpClientMock("")}
result.SetApiKeyAndSecret(tt.apiKey, tt.apiSecret)

if result.apiKey != tt.result.apiKey {
Expand All @@ -39,7 +39,7 @@ func TestSetApiKeyAndSecret(t *testing.T) {
func TestRequestBearerToken(t *testing.T) {
var tests = []struct {
mockRequest func(opts *httpclient.RequestOpts) (*http.Response, error)
result *requestBearerTokenResponse
result *RequestBearerTokenResponse
}{
{func(opts *httpclient.RequestOpts) (*http.Response, error) {

Expand All @@ -55,7 +55,7 @@ func TestRequestBearerToken(t *testing.T) {
Body: body,
}, nil
},
&requestBearerTokenResponse{
&RequestBearerTokenResponse{
TokenType: "bearer",
AccessToken: "123Token456",
}},
Expand Down
11 changes: 6 additions & 5 deletions twitterstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@ package twitterstream

import "github.com/fallenstedt/twitter-stream/httpclient"

type twitterApi struct {
type TwitterApi struct {
Rules IRules
Stream IStream
}

// NewTokenGenerator creates a tokenGenerator which can request a Bearer token using a twitter api key and secret.
func NewTokenGenerator() *tokenGenerator {

// NewTokenGenerator creates a TokenGenerator which can request a Bearer token using a twitter api key and secret.
func NewTokenGenerator() ITokenGenerator {
client := httpclient.NewHttpClient("")
tokenGenerator := newTokenGenerator(client)
return tokenGenerator
}

// NewTwitterStream consumes a twitter Bearer token.
// It is used to interact with Twitter's v2 filtered streaming API
func NewTwitterStream(token string) *twitterApi {
func NewTwitterStream(token string) *TwitterApi {
client := httpclient.NewHttpClient(token)
rules := newRules(client)
stream := newStream(client, newStreamResponseBodyReader())
return &twitterApi{Rules: rules, Stream: stream}
return &TwitterApi{Rules: rules, Stream: stream}
}

0 comments on commit f43d02a

Please sign in to comment.