diff --git a/README b/README index 312b6bd..0cd411e 100644 --- a/README +++ b/README @@ -3,7 +3,7 @@ Anaconda [![Build Status](https://travis-ci.org/ChimeraCoder/anaconda.svg?branch=master)](https://travis-ci.org/ChimeraCoder/anaconda) [![Build Status](https://ci.appveyor.com/api/projects/status/63pi6csod8bps80i/branch/master?svg=true)](https://ci.appveyor.com/project/ChimeraCoder/anaconda/branch/master) [![GoDoc](https://godoc.org/github.com/ChimeraCoder/anaconda?status.svg)](https://godoc.org/github.com/ChimeraCoder/anaconda) -Anaconda is a simple, transparent Go package for accessing version 1.1 of the Twitter API. +Anaconda is a simple, transparent Go package for accessing version 1.1 of the Twitter API. Successful API queries return native Go structs that can be used immediately, with no need for type assertions. @@ -17,9 +17,7 @@ Examples If you already have the access token (and secret) for your user (Twitter provides this for your own account on the developer portal), creating the client is simple: ````go -anaconda.SetConsumerKey("your-consumer-key") -anaconda.SetConsumerSecret("your-consumer-secret") -api := anaconda.NewTwitterApi("your-access-token", "your-access-token-secret") +api := anaconda.NewTwitterApiWithCredentials("your-access-token", "your-access-token-secret", "your-consumer-key", "your-consumer-secret") ```` ### Queries @@ -30,9 +28,9 @@ Queries are conducted using a pointer to an authenticated `TwitterApi` struct. I searchResult, _ := api.GetSearch("golang", nil) for _ , tweet := range searchResult.Statuses { fmt.Println(tweet.Text) -} +} ```` -Certain endpoints allow separate optional parameter; if desired, these can be passed as the final parameter. +Certain endpoints allow separate optional parameter; if desired, these can be passed as the final parameter. ````go //Perhaps we want 30 values instead of the default 15 diff --git a/example_test.go b/example_test.go index 89476b7..39c3f31 100644 --- a/example_test.go +++ b/example_test.go @@ -10,14 +10,11 @@ import ( // Initialize an client library for a given user. // This only needs to be done *once* per user func ExampleTwitterApi_InitializeClient() { - anaconda.SetConsumerKey("your-consumer-key") - anaconda.SetConsumerSecret("your-consumer-secret") - api := anaconda.NewTwitterApi(ACCESS_TOKEN, ACCESS_TOKEN_SECRET) + api := anaconda.NewTwitterApiWithCredentials(ACCESS_TOKEN, ACCESS_TOKEN_SECRET, "your-consumer-key", "your-consumer-secret") fmt.Println(*api.Credentials) } func ExampleTwitterApi_GetSearch() { - anaconda.SetConsumerKey("your-consumer-key") anaconda.SetConsumerSecret("your-consumer-secret") api := anaconda.NewTwitterApi("your-access-token", "your-access-token-secret") diff --git a/streaming.go b/streaming.go index 6914512..5703e16 100644 --- a/streaming.go +++ b/streaming.go @@ -223,9 +223,9 @@ func jsonToKnownType(j []byte) interface{} { func (s *Stream) requestStream(urlStr string, v url.Values, method int) (resp *http.Response, err error) { switch method { case _GET: - return oauthClient.Get(s.api.HttpClient, s.api.Credentials, urlStr, v) + return s.api.oauthClient.Get(s.api.HttpClient, s.api.Credentials, urlStr, v) case _POST: - return oauthClient.Post(s.api.HttpClient, s.api.Credentials, urlStr, v) + return s.api.oauthClient.Post(s.api.HttpClient, s.api.Credentials, urlStr, v) default: } return nil, fmt.Errorf("HTTP method not yet supported") diff --git a/twitter.go b/twitter.go index 0a9902c..3dc68ff 100644 --- a/twitter.go +++ b/twitter.go @@ -63,13 +63,12 @@ const ( UploadBaseUrl = "https://upload.twitter.com/1.1" ) -var oauthClient = oauth.Client{ - TemporaryCredentialRequestURI: "https://api.twitter.com/oauth/request_token", - ResourceOwnerAuthorizationURI: "https://api.twitter.com/oauth/authenticate", - TokenRequestURI: "https://api.twitter.com/oauth/access_token", -} +var ( + oauthCredentials oauth.Credentials +) type TwitterApi struct { + oauthClient oauth.Client Credentials *oauth.Credentials queryQueue chan query bucket *tokenbucket.Bucket @@ -109,6 +108,12 @@ func NewTwitterApi(access_token string, access_token_secret string) *TwitterApi //A non-buffered channel will cause blocking when multiple queries are made at the same time queue := make(chan query) c := &TwitterApi{ + oauthClient: oauth.Client{ + TemporaryCredentialRequestURI: "https://api.twitter.com/oauth/request_token", + ResourceOwnerAuthorizationURI: "https://api.twitter.com/oauth/authenticate", + TokenRequestURI: "https://api.twitter.com/oauth/access_token", + Credentials: oauthCredentials, + }, Credentials: &oauth.Credentials{ Token: access_token, Secret: access_token_secret, @@ -124,16 +129,25 @@ func NewTwitterApi(access_token string, access_token_secret string) *TwitterApi return c } +//NewTwitterApiWithCredentials takes an app-specific consumer key and secret, along with a user-specific access token and secret and returns a TwitterApi struct for that user. +//The TwitterApi struct can be used for accessing any of the endpoints available. +func NewTwitterApiWithCredentials(access_token string, access_token_secret string, consumer_key string, consumer_secret string) *TwitterApi { + api := NewTwitterApi(access_token, access_token_secret) + api.oauthClient.Credentials.Token = consumer_key + api.oauthClient.Credentials.Secret = consumer_secret + return api +} + //SetConsumerKey will set the application-specific consumer_key used in the initial OAuth process //This key is listed on https://dev.twitter.com/apps/YOUR_APP_ID/show func SetConsumerKey(consumer_key string) { - oauthClient.Credentials.Token = consumer_key + oauthCredentials.Token = consumer_key } //SetConsumerSecret will set the application-specific secret used in the initial OAuth process //This secret is listed on https://dev.twitter.com/apps/YOUR_APP_ID/show func SetConsumerSecret(consumer_secret string) { - oauthClient.Credentials.Secret = consumer_secret + oauthCredentials.Secret = consumer_secret } // ReturnRateLimitError specifies behavior when the Twitter API returns a rate-limit error. @@ -170,20 +184,20 @@ func (c *TwitterApi) SetBaseUrl(baseUrl string) { //AuthorizationURL generates the authorization URL for the first part of the OAuth handshake. //Redirect the user to this URL. -//This assumes that the consumer key has already been set (using SetConsumerKey). -func AuthorizationURL(callback string) (string, *oauth.Credentials, error) { - tempCred, err := oauthClient.RequestTemporaryCredentials(http.DefaultClient, callback, nil) +//This assumes that the consumer key has already been set (using SetConsumerKey or NewTwitterApiWithCredentials). +func (c *TwitterApi) AuthorizationURL(callback string) (string, *oauth.Credentials, error) { + tempCred, err := c.oauthClient.RequestTemporaryCredentials(http.DefaultClient, callback, nil) if err != nil { return "", nil, err } - return oauthClient.AuthorizationURL(tempCred, nil), tempCred, nil + return c.oauthClient.AuthorizationURL(tempCred, nil), tempCred, nil } // GetCredentials gets the access token using the verifier received with the callback URL and the // credentials in the first part of the handshake. GetCredentials implements the third part of the OAuth handshake. // The returned url.Values holds the access_token, the access_token_secret, the user_id and the screen_name. -func GetCredentials(tempCred *oauth.Credentials, verifier string) (*oauth.Credentials, url.Values, error) { - return oauthClient.RequestToken(http.DefaultClient, tempCred, verifier) +func (c *TwitterApi) GetCredentials(tempCred *oauth.Credentials, verifier string) (*oauth.Credentials, url.Values, error) { + return c.oauthClient.RequestToken(http.DefaultClient, tempCred, verifier) } func defaultValues(v url.Values) url.Values { @@ -204,7 +218,7 @@ func cleanValues(v url.Values) url.Values { // apiGet issues a GET request to the Twitter API and decodes the response JSON to data. func (c TwitterApi) apiGet(urlStr string, form url.Values, data interface{}) error { form = defaultValues(form) - resp, err := oauthClient.Get(c.HttpClient, c.Credentials, urlStr, form) + resp, err := c.oauthClient.Get(c.HttpClient, c.Credentials, urlStr, form) if err != nil { return err } @@ -214,7 +228,7 @@ func (c TwitterApi) apiGet(urlStr string, form url.Values, data interface{}) err // apiPost issues a POST request to the Twitter API and decodes the response JSON to data. func (c TwitterApi) apiPost(urlStr string, form url.Values, data interface{}) error { - resp, err := oauthClient.Post(c.HttpClient, c.Credentials, urlStr, form) + resp, err := c.oauthClient.Post(c.HttpClient, c.Credentials, urlStr, form) if err != nil { return err } @@ -224,7 +238,7 @@ func (c TwitterApi) apiPost(urlStr string, form url.Values, data interface{}) er // apiDel issues a DELETE request to the Twitter API and decodes the response JSON to data. func (c TwitterApi) apiDel(urlStr string, form url.Values, data interface{}) error { - resp, err := oauthClient.Delete(c.HttpClient, c.Credentials, urlStr, form) + resp, err := c.oauthClient.Delete(c.HttpClient, c.Credentials, urlStr, form) if err != nil { return err } @@ -234,7 +248,7 @@ func (c TwitterApi) apiDel(urlStr string, form url.Values, data interface{}) err // apiPut issues a PUT request to the Twitter API and decodes the response JSON to data. func (c TwitterApi) apiPut(urlStr string, form url.Values, data interface{}) error { - resp, err := oauthClient.Put(c.HttpClient, c.Credentials, urlStr, form) + resp, err := c.oauthClient.Put(c.HttpClient, c.Credentials, urlStr, form) if err != nil { return err } diff --git a/twitter_test.go b/twitter_test.go index 4b2be7f..23223a1 100644 --- a/twitter_test.go +++ b/twitter_test.go @@ -126,6 +126,15 @@ func Test_TwitterApi_NewTwitterApi(t *testing.T) { } } +// Test that creating a TwitterApi client creates a client with non-empty OAuth credentials +func Test_TwitterApi_NewTwitterApiWithCredentials(t *testing.T) { + apiLocal := anaconda.NewTwitterApiWithCredentials(ACCESS_TOKEN, ACCESS_TOKEN_SECRET, CONSUMER_KEY, CONSUMER_SECRET) + + if apiLocal.Credentials == nil { + t.Fatalf("Twitter Api client has empty (nil) credentials") + } +} + // Test that the GetSearch function actually works and returns non-empty results func Test_TwitterApi_GetSearch(t *testing.T) { search_result, err := api.GetSearch("golang", nil)