diff --git a/example/create_rules_example.go b/example/create_rules_example.go index ad8f26c..532e383 100644 --- a/example/create_rules_example.go +++ b/example/create_rules_example.go @@ -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() { diff --git a/httpclient/httpclient.go b/httpclient/httpclient.go index d2926f2..dd0af7e 100644 --- a/httpclient/httpclient.go +++ b/httpclient/httpclient.go @@ -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" diff --git a/rules.go b/rules.go index cd656c1..cffb97b 100644 --- a/rules.go +++ b/rules.go @@ -44,7 +44,7 @@ type ( } ) -func newRules(httpClient httpclient.IHttpClient) *rules { +func newRules(httpClient httpclient.IHttpClient) IRules { return &rules{httpClient: httpClient} } diff --git a/stream.go b/stream.go index 7f0a343..6b93649 100644 --- a/stream.go +++ b/stream.go @@ -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 diff --git a/stream_test.go b/stream_test.go index 123d41f..0d91f26 100644 --- a/stream_test.go +++ b/stream_test.go @@ -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 diff --git a/stream_utils.go b/stream_utils.go index df5ac5d..d2e1aea 100644 --- a/stream_utils.go +++ b/stream_utils.go @@ -9,7 +9,6 @@ import ( "bufio" "bytes" "io" - "time" ) // stopped returns true if the done channel receives, false otherwise. @@ -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 { @@ -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{} } diff --git a/token_generator.go b/token_generator.go index 655b802..e2d2fbc 100644 --- a/token_generator.go +++ b/token_generator.go @@ -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 { @@ -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)) } diff --git a/token_generator_test.go b/token_generator_test.go index 40decc7..08d9238 100644 --- a/token_generator_test.go +++ b/token_generator_test.go @@ -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 { @@ -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) { @@ -55,7 +55,7 @@ func TestRequestBearerToken(t *testing.T) { Body: body, }, nil }, - &requestBearerTokenResponse{ + &RequestBearerTokenResponse{ TokenType: "bearer", AccessToken: "123Token456", }}, diff --git a/twitterstream.go b/twitterstream.go index 7e58292..06f52cd 100644 --- a/twitterstream.go +++ b/twitterstream.go @@ -3,13 +3,14 @@ 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 @@ -17,9 +18,9 @@ func NewTokenGenerator() *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} }