From 778304285197d20ec624559d73ddb4ce279e8d89 Mon Sep 17 00:00:00 2001 From: Sean Date: Mon, 14 Feb 2022 20:02:07 -0700 Subject: [PATCH] Feature/issue 102 rate limit handling (#104) * added rate limits * added rate limit to callouts * add rate limiting to hide replies * rate limit error helper * added response decode error * updated the error message and name * updated unit tests * add unit test for rate limit helper * added the rate limit example * updated the readme * updated readme * update for release migration * fix per golint --- v2/README.md | 44 ++ v2/_examples/README.md | 7 + v2/_examples/rate-limit/main.go | 63 +++ v2/_examples/tweet-hide-replies/main.go | 11 +- v2/client.go | 720 ++++++++++++++++++++---- v2/client_blocks_test.go | 36 ++ v2/client_likes_test.go | 48 ++ v2/client_list_followers_test.go | 48 ++ v2/client_list_lookup_test.go | 36 ++ v2/client_list_manage_test.go | 36 ++ v2/client_list_members_test.go | 48 ++ v2/client_mutes_test.go | 36 ++ v2/client_pinned_list_test.go | 36 ++ v2/client_retweet_test.go | 36 ++ v2/client_tweet_hide_replies_test.go | 26 +- v2/client_tweet_lookup_test.go | 12 + v2/client_tweet_manage_test.go | 24 + v2/client_tweet_recent_counts_test.go | 12 + v2/client_tweet_search_test.go | 67 +++ v2/client_tweet_stream_test.go | 7 + v2/client_user_followers_test.go | 12 + v2/client_user_following_test.go | 36 ++ v2/client_user_lookup_test.go | 36 ++ v2/client_user_mention_timeline_test.go | 12 + v2/client_user_tweet_timeline_test.go | 12 + v2/error_obj.go | 30 +- v2/list_lookup.go | 38 +- v2/list_manage.go | 27 +- v2/rate_limits.go | 66 +++ v2/rate_limits_test.go | 148 +++++ v2/tweet_counts.go | 1 + v2/tweet_likes.go | 5 +- v2/tweet_manage.go | 6 +- v2/tweet_raw.go | 24 +- v2/tweet_search.go | 24 +- v2/tweet_stream.go | 13 +- v2/user_blocks.go | 11 +- v2/user_likes.go | 11 +- v2/user_mutes.go | 11 +- v2/user_raw.go | 19 +- v2/user_retweet.go | 11 +- 41 files changed, 1700 insertions(+), 206 deletions(-) create mode 100644 v2/_examples/rate-limit/main.go create mode 100644 v2/rate_limits.go create mode 100644 v2/rate_limits_test.go diff --git a/v2/README.md b/v2/README.md index b06241a..8a5ebd4 100644 --- a/v2/README.md +++ b/v2/README.md @@ -22,6 +22,27 @@ The following are changes between `v1` and `v2` of the library. * One structure for all endpoint callouts. In `v1` there were two structures, `Tweet` and `User`, to preform callouts. This required management of two structures and knowledge which structure contained the desired method callouts. At the time, the grouping looked logical. However with the addtion of the timeline endpoints, it makes more sense to have a single struture `Client` to handle all of the callouts. If the user would like to separate the functionality, interfaces can be used to achieve this. * Endpoint methods will return the entire response. One of the major drawbacks of `v1` was the object returned was not the entire response sent by the callout. For example, the `errors` object in the response is included in `v1` response which does not allow the caller to properly handle partial errors. In `v2`, the first focus is to return the response from twitter to allow the caller to use it as they see fit. However, it does not mean that methods can not be added to the response object to provide groupings, etc. +### Breaking Changes +These are some breaking changes and what release they are from. These type of changes will try to be avoided but if necessary for a better library, it will be done. + +#### v2.0.0-beta11 +* The client callout for tweet hide reply, `TweetHideReplies`, has been changed to return the response instead of just an error. This allow for the data and the rate limits of the callout to be returned. +##### Migration +```go + // old way + err := client.TweetHideReplies(context.Background(), id, true) + if err != nil { + // handle error + } +``` +```go + // new way + hideResponse, err := client.TweetHideReplies(context.Background(), id, true) + if err != nil { + // handle error + } +``` + ## Features Here are the current twitter `v2` API features supported: * [Tweet Lookup](https://developer.twitter.com/en/docs/twitter-api/tweets/lookup/introduction) @@ -88,6 +109,29 @@ Here are the current twitter `v2` API features supported: * [user followed lists](./examples/user-followed-lists) * [list followers](./examples/list-followers) +## Rate Limiting +With each response, the rate limits from the response header is returned. This allows the caller to manage any limits that are imposed. Along with the response, errors that are returned may have rate limits as well. If the error occurs after the request is sent, then rate limits may apply and are returned. + +There is an example of rate limiting from a response [here](./examples/rate-limit). + +This is an example of a twitter callout and if the limits have been reached, then it will backoff and try again. +```go +func TweetLikes(ctx context.Context, id string, client *twitter.Client) (*twitter.TweetLikesLookupResponse, error) { + var er *ErrorResponse + + opts := twitter.ListUserMembersOpts{ + MaxResults: 1, + } + tweetResponse, err := client.TweetLikesLookup(ctx, id, opts) + + if rateLimit, has := twitter.RateLimitFromError(err); has && rateLimit.Remaining == 0 { + time.Sleep(time.Until(rateLimit.Reset.Time())) + return client.TweetLikesLookup(ctx, id, opts) + } + return tweetResponse, err +} +``` + ## Examples Much like `v1`, there is an `_example` directory to demostrate library usage. Refer to the [readme](./_examples) for more information. diff --git a/v2/_examples/README.md b/v2/_examples/README.md index 6a8353c..24cb442 100644 --- a/v2/_examples/README.md +++ b/v2/_examples/README.md @@ -307,3 +307,10 @@ This [example](./list-followers) demonstrates list followers API. ``` go run *.go -token=YOUR_API_TOKEN -list_id='LIST_ID' ``` + +## Rate Limit +This [example](./rate-limit) demonstrates rate limiting API. + +``` +go run *.go -token=YOUR_API_TOKEN +``` diff --git a/v2/_examples/rate-limit/main.go b/v2/_examples/rate-limit/main.go new file mode 100644 index 0000000..2a01f7b --- /dev/null +++ b/v2/_examples/rate-limit/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "net/http" + "time" + + twitter "github.com/g8rswimmer/go-twitter/v2" +) + +type authorize struct { + Token string +} + +func (a authorize) Add(req *http.Request) { + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", a.Token)) +} + +/** + In order to run, the user will need to provide the bearer token and the list of tweet ids. +**/ +func main() { + token := flag.String("token", "", "twitter API token") + flag.Parse() + + client := &twitter.Client{ + Authorizer: authorize{ + Token: *token, + }, + Client: http.DefaultClient, + Host: "https://api.twitter.com", + } + + fmt.Println("Rate Limiting Example\n\n") + + var rateLimit *twitter.RateLimit + + rateLimit = callout(client) + fmt.Printf("Rate Limits: %v\n", *rateLimit) + rateLimit = callout(client) + fmt.Printf("Rate Limits: %v\n\n", *rateLimit) + + fmt.Printf("Wait for reset %v\n", rateLimit.Reset.Time()) + time.Sleep(time.Until(rateLimit.Reset.Time())) + fmt.Println("Rate Limits are reset") + + rateLimit = callout(client) + fmt.Printf("Rate Limits: %v\n", *rateLimit) +} + +func callout(client *twitter.Client) *twitter.RateLimit { + opts := twitter.ListUserMembersOpts{ + MaxResults: 1, + } + listResponse, err := client.ListUserMembers(context.Background(), "84839422", opts) + if err != nil { + log.Panicf("list members error: %v", err) + } + return listResponse.RateLimit +} diff --git a/v2/_examples/tweet-hide-replies/main.go b/v2/_examples/tweet-hide-replies/main.go index 9212388..e92d24a 100644 --- a/v2/_examples/tweet-hide-replies/main.go +++ b/v2/_examples/tweet-hide-replies/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/json" "flag" "fmt" "log" @@ -43,9 +44,15 @@ func main() { fmt.Println("Callout to tweet hide replies") - if err := client.TweetHideReplies(context.Background(), *id, hideBool); err != nil { + hideResponse, err := client.TweetHideReplies(context.Background(), *id, hideBool) + if err != nil { log.Panicf("tweet hide replies error: %v", err) } - fmt.Printf("tweet %s hide replies %v", *ids, hideBool) + enc, err := json.MarshalIndent(hideResponse, "", " ") + if err != nil { + log.Panic(err) + } + fmt.Println(string(enc)) + } diff --git a/v2/client.go b/v2/client.go index 1744c99..8206e78 100644 --- a/v2/client.go +++ b/v2/client.go @@ -64,6 +64,8 @@ func (c *Client) CreateTweet(ctx context.Context, tweet CreateTweetRequest) (*Cr decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusCreated { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -71,16 +73,23 @@ func (c *Client) CreateTweet(ctx context.Context, tweet CreateTweetRequest) (*Cr Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } raw := &CreateTweetResponse{} if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("create tweet decode response %w", err) + return nil, &ResponseDecodeError{ + Name: "create tweet", + Err: err, + RateLimit: rl, + } } + raw.RateLimit = rl return raw, nil } @@ -106,6 +115,8 @@ func (c *Client) DeleteTweet(ctx context.Context, id string) (*DeleteTweetRespon decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -113,16 +124,23 @@ func (c *Client) DeleteTweet(ctx context.Context, id string) (*DeleteTweetRespon Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } raw := &DeleteTweetResponse{} if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("delete tweet decode response %w", err) + return nil, &ResponseDecodeError{ + Name: "delete tweet", + Err: err, + RateLimit: rl, + } } + raw.RateLimit = rl return raw, nil } @@ -160,6 +178,8 @@ func (c *Client) TweetLookup(ctx context.Context, ids []string, opts TweetLookup decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -167,9 +187,11 @@ func (c *Client) TweetLookup(ctx context.Context, ids []string, opts TweetLookup Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -178,7 +200,11 @@ func (c *Client) TweetLookup(ctx context.Context, ids []string, opts TweetLookup case len(ids) == 1: single := &tweetraw{} if err := decoder.Decode(single); err != nil { - return nil, fmt.Errorf("tweet lookup single dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "tweet lookup", + Err: err, + RateLimit: rl, + } } raw.Tweets = make([]*TweetObj, 1) raw.Tweets[0] = single.Tweet @@ -186,11 +212,16 @@ func (c *Client) TweetLookup(ctx context.Context, ids []string, opts TweetLookup raw.Errors = single.Errors default: if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("tweet lookup dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "tweet lookup ", + Err: err, + RateLimit: rl, + } } } return &TweetLookupResponse{ - Raw: raw, + Raw: raw, + RateLimit: rl, }, nil } @@ -228,6 +259,8 @@ func (c *Client) UserLookup(ctx context.Context, ids []string, opts UserLookupOp decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -235,9 +268,11 @@ func (c *Client) UserLookup(ctx context.Context, ids []string, opts UserLookupOp Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -246,7 +281,11 @@ func (c *Client) UserLookup(ctx context.Context, ids []string, opts UserLookupOp case len(ids) == 1: single := &userraw{} if err := decoder.Decode(single); err != nil { - return nil, fmt.Errorf("user lookup single dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "user lookup", + Err: err, + RateLimit: rl, + } } raw.Users = make([]*UserObj, 1) raw.Users[0] = single.User @@ -254,11 +293,16 @@ func (c *Client) UserLookup(ctx context.Context, ids []string, opts UserLookupOp raw.Errors = single.Errors default: if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("user lookup dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "user lookup", + Err: err, + RateLimit: rl, + } } } return &UserLookupResponse{ - Raw: raw, + Raw: raw, + RateLimit: rl, }, nil } @@ -288,6 +332,8 @@ func (c *Client) UserRetweetLookup(ctx context.Context, tweetID string, opts Use decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -295,9 +341,11 @@ func (c *Client) UserRetweetLookup(ctx context.Context, tweetID string, opts Use Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -306,11 +354,16 @@ func (c *Client) UserRetweetLookup(ctx context.Context, tweetID string, opts Use Meta *UserRetweetMeta `json:"meta"` }{} if err := decoder.Decode(&raw); err != nil { - return nil, fmt.Errorf("user retweet lookup dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "user retweet lookup", + Err: err, + RateLimit: rl, + } } return &UserRetweetLookupResponse{ - Raw: raw.UserRetweetRaw, - Meta: raw.Meta, + Raw: raw.UserRetweetRaw, + Meta: raw.Meta, + RateLimit: rl, }, nil } @@ -348,6 +401,8 @@ func (c *Client) UserNameLookup(ctx context.Context, usernames []string, opts Us decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -355,9 +410,11 @@ func (c *Client) UserNameLookup(ctx context.Context, usernames []string, opts Us Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -366,7 +423,11 @@ func (c *Client) UserNameLookup(ctx context.Context, usernames []string, opts Us case len(usernames) == 1: single := &userraw{} if err := decoder.Decode(single); err != nil { - return nil, fmt.Errorf("username lookup single dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "username lookup", + Err: err, + RateLimit: rl, + } } raw.Users = make([]*UserObj, 1) raw.Users[0] = single.User @@ -374,11 +435,16 @@ func (c *Client) UserNameLookup(ctx context.Context, usernames []string, opts Us raw.Errors = single.Errors default: if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("username lookup dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "username lookup", + Err: err, + RateLimit: rl, + } } } return &UserLookupResponse{ - Raw: raw, + Raw: raw, + RateLimit: rl, }, nil } @@ -402,6 +468,8 @@ func (c *Client) AuthUserLookup(ctx context.Context, opts UserLookupOpts) (*User decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -409,15 +477,21 @@ func (c *Client) AuthUserLookup(ctx context.Context, opts UserLookupOpts) (*User Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } single := &userraw{} if err := decoder.Decode(single); err != nil { - return nil, fmt.Errorf("user lookup single dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "auth user lookup", + Err: err, + RateLimit: rl, + } } raw := &UserRaw{} raw.Users = make([]*UserObj, 1) @@ -426,7 +500,8 @@ func (c *Client) AuthUserLookup(ctx context.Context, opts UserLookupOpts) (*User raw.Errors = single.Errors return &UserLookupResponse{ - Raw: raw, + Raw: raw, + RateLimit: rl, }, nil } @@ -461,6 +536,9 @@ func (c *Client) TweetRecentSearch(ctx context.Context, query string, opts Tweet if err != nil { return nil, fmt.Errorf("tweet recent search response read: %w", err) } + + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := json.Unmarshal(respBytes, e); err != nil { @@ -468,23 +546,34 @@ func (c *Client) TweetRecentSearch(ctx context.Context, query string, opts Tweet Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } recentSearch := &TweetRecentSearchResponse{ - Raw: &TweetRaw{}, - Meta: &TweetRecentSearchMeta{}, + Raw: &TweetRaw{}, + Meta: &TweetRecentSearchMeta{}, + RateLimit: rl, } if err := json.Unmarshal(respBytes, recentSearch.Raw); err != nil { - return nil, fmt.Errorf("tweet recent search raw response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "tweet recent search", + Err: err, + RateLimit: rl, + } } if err := json.Unmarshal(respBytes, recentSearch); err != nil { - return nil, fmt.Errorf("tweet recent search meta response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "tweet recent search", + Err: err, + RateLimit: rl, + } } return recentSearch, nil @@ -528,6 +617,8 @@ func (c *Client) TweetSearchStreamAddRule(ctx context.Context, rules []TweetSear decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusCreated { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -535,16 +626,23 @@ func (c *Client) TweetSearchStreamAddRule(ctx context.Context, rules []TweetSear Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } ruleResponse := &TweetSearchStreamAddRuleResponse{} if err := decoder.Decode(ruleResponse); err != nil { - return nil, fmt.Errorf("tweet search stream add rule json response %w", err) + return nil, &ResponseDecodeError{ + Name: "tweet search stream add rule", + Err: err, + RateLimit: rl, + } } + ruleResponse.RateLimit = rl return ruleResponse, nil } @@ -593,6 +691,8 @@ func (c *Client) TweetSearchStreamDeleteRuleByID(ctx context.Context, ruleIDs [] decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -600,16 +700,23 @@ func (c *Client) TweetSearchStreamDeleteRuleByID(ctx context.Context, ruleIDs [] Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } ruleResponse := &TweetSearchStreamDeleteRuleResponse{} if err := decoder.Decode(ruleResponse); err != nil { - return nil, fmt.Errorf("tweet search stream delete rule json response %w", err) + return nil, &ResponseDecodeError{ + Name: "tweet search stream delete rule ny id", + Err: err, + RateLimit: rl, + } } + ruleResponse.RateLimit = rl return ruleResponse, nil } @@ -654,6 +761,8 @@ func (c *Client) TweetSearchStreamDeleteRuleByValue(ctx context.Context, ruleVal decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -661,16 +770,23 @@ func (c *Client) TweetSearchStreamDeleteRuleByValue(ctx context.Context, ruleVal Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } ruleResponse := &TweetSearchStreamDeleteRuleResponse{} if err := decoder.Decode(ruleResponse); err != nil { - return nil, fmt.Errorf("tweet search stream delete rule json response %w", err) + return nil, &ResponseDecodeError{ + Name: "tweet search stream delete rule by value", + Err: err, + RateLimit: rl, + } } + ruleResponse.RateLimit = rl return ruleResponse, nil } @@ -700,6 +816,8 @@ func (c *Client) TweetSearchStreamRules(ctx context.Context, ruleIDs []TweetSear decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -707,16 +825,23 @@ func (c *Client) TweetSearchStreamRules(ctx context.Context, ruleIDs []TweetSear Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } ruleResponse := &TweetSearchStreamRulesResponse{} if err := decoder.Decode(ruleResponse); err != nil { - return nil, fmt.Errorf("tweet search stream rules json response %w", err) + return nil, &ResponseDecodeError{ + Name: "tweet search stream rules", + Err: err, + RateLimit: rl, + } } + ruleResponse.RateLimit = rl return ruleResponse, nil } @@ -742,6 +867,8 @@ func (c *Client) TweetSearchStream(ctx context.Context, opts TweetSearchStreamOp return nil, fmt.Errorf("tweet search stream response: %w", err) } + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { defer resp.Body.Close() e := &ErrorResponse{} @@ -750,13 +877,17 @@ func (c *Client) TweetSearchStream(ctx context.Context, opts TweetSearchStreamOp Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } - return StartTweetStream(resp.Body), nil + stream := StartTweetStream(resp.Body) + stream.RateLimit = rl + return stream, nil } // TweetRecentCounts will return a recent tweet counts based of a query @@ -786,6 +917,8 @@ func (c *Client) TweetRecentCounts(ctx context.Context, query string, opts Tweet } defer resp.Body.Close() + rl := rateFromHeader(resp.Header) + respBytes, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("tweet recent counts response read: %w", err) @@ -797,9 +930,11 @@ func (c *Client) TweetRecentCounts(ctx context.Context, query string, opts Tweet Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -809,9 +944,13 @@ func (c *Client) TweetRecentCounts(ctx context.Context, query string, opts Tweet } if err := json.Unmarshal(respBytes, recentCounts); err != nil { - return nil, fmt.Errorf("tweet recent counts response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "tweet recent counts", + Err: err, + RateLimit: rl, + } } - + recentCounts.RateLimit = rl return recentCounts, nil } @@ -837,6 +976,8 @@ func (c *Client) UserFollowingLookup(ctx context.Context, id string, opts UserFo } defer resp.Body.Close() + rl := rateFromHeader(resp.Header) + respBytes, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("user following lookup response read: %w", err) @@ -848,9 +989,11 @@ func (c *Client) UserFollowingLookup(ctx context.Context, id string, opts UserFo Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -860,13 +1003,21 @@ func (c *Client) UserFollowingLookup(ctx context.Context, id string, opts UserFo } if err := json.Unmarshal(respBytes, followingLookup.Raw); err != nil { - return nil, fmt.Errorf("user following lookup raw response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user following lookup", + Err: err, + RateLimit: rl, + } } if err := json.Unmarshal(respBytes, followingLookup); err != nil { - return nil, fmt.Errorf("user following lookup meta response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user following lookup", + Err: err, + RateLimit: rl, + } } - + followingLookup.RateLimit = rl return followingLookup, nil } @@ -905,6 +1056,8 @@ func (c *Client) UserFollows(ctx context.Context, userID, targetUserID string) ( decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -912,16 +1065,23 @@ func (c *Client) UserFollows(ctx context.Context, userID, targetUserID string) ( Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } raw := &UserFollowsResponse{} if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("user follows decode response %w", err) + return nil, &ResponseDecodeError{ + Name: "user follows", + Err: err, + RateLimit: rl, + } } + raw.RateLimit = rl return raw, nil } @@ -951,6 +1111,8 @@ func (c *Client) DeleteUserFollows(ctx context.Context, userID, targetUserID str decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -958,16 +1120,23 @@ func (c *Client) DeleteUserFollows(ctx context.Context, userID, targetUserID str Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } raw := &UserDeleteFollowsResponse{} if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("user delete follows decode response %w", err) + return nil, &ResponseDecodeError{ + Name: "delete user follows", + Err: err, + RateLimit: rl, + } } + raw.RateLimit = rl return raw, nil } @@ -993,6 +1162,8 @@ func (c *Client) UserFollowersLookup(ctx context.Context, id string, opts UserFo } defer resp.Body.Close() + rl := rateFromHeader(resp.Header) + respBytes, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("user followers lookup response read: %w", err) @@ -1004,9 +1175,11 @@ func (c *Client) UserFollowersLookup(ctx context.Context, id string, opts UserFo Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -1016,13 +1189,21 @@ func (c *Client) UserFollowersLookup(ctx context.Context, id string, opts UserFo } if err := json.Unmarshal(respBytes, followersLookup.Raw); err != nil { - return nil, fmt.Errorf("user followers lookup raw response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user followers lookup", + Err: err, + RateLimit: rl, + } } if err := json.Unmarshal(respBytes, followersLookup); err != nil { - return nil, fmt.Errorf("user followers lookup meta response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user followers lookup", + Err: err, + RateLimit: rl, + } } - + followersLookup.RateLimit = rl return followersLookup, nil } @@ -1048,6 +1229,8 @@ func (c *Client) UserTweetTimeline(ctx context.Context, userID string, opts User } defer resp.Body.Close() + rl := rateFromHeader(resp.Header) + respBytes, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("user tweet timeline response read: %w", err) @@ -1059,9 +1242,11 @@ func (c *Client) UserTweetTimeline(ctx context.Context, userID string, opts User Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -1071,13 +1256,21 @@ func (c *Client) UserTweetTimeline(ctx context.Context, userID string, opts User } if err := json.Unmarshal(respBytes, timeline.Raw); err != nil { - return nil, fmt.Errorf("user tweet timeline raw response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user tweet timeline", + Err: err, + RateLimit: rl, + } } if err := json.Unmarshal(respBytes, timeline); err != nil { - return nil, fmt.Errorf("user tweet timeline meta response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user tweet timeline", + Err: err, + RateLimit: rl, + } } - + timeline.RateLimit = rl return timeline, nil } @@ -1103,6 +1296,8 @@ func (c *Client) UserMentionTimeline(ctx context.Context, userID string, opts Us } defer resp.Body.Close() + rl := rateFromHeader(resp.Header) + respBytes, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("user mention timeline response read: %w", err) @@ -1114,9 +1309,11 @@ func (c *Client) UserMentionTimeline(ctx context.Context, userID string, opts Us Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -1126,20 +1323,28 @@ func (c *Client) UserMentionTimeline(ctx context.Context, userID string, opts Us } if err := json.Unmarshal(respBytes, timeline.Raw); err != nil { - return nil, fmt.Errorf("user mention timeline raw response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user mention timeline", + Err: err, + RateLimit: rl, + } } if err := json.Unmarshal(respBytes, timeline); err != nil { - return nil, fmt.Errorf("user mention timeline meta response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user mention timeline", + Err: err, + RateLimit: rl, + } } - + timeline.RateLimit = rl return timeline, nil } // TweetHideReplies will hide the replies for a given tweet -func (c Client) TweetHideReplies(ctx context.Context, id string, hide bool) error { +func (c Client) TweetHideReplies(ctx context.Context, id string, hide bool) (*TweetHideReplyResponse, error) { if len(id) == 0 { - return fmt.Errorf("tweet hide replies: id must be present %w", ErrParameter) + return nil, fmt.Errorf("tweet hide replies: id must be present %w", ErrParameter) } type body struct { Hidden bool `json:"hidden"` @@ -1149,49 +1354,50 @@ func (c Client) TweetHideReplies(ctx context.Context, id string, hide bool) erro } enc, err := json.Marshal(rb) if err != nil { - return fmt.Errorf("tweet hide replies: request body marshal %w", err) + return nil, fmt.Errorf("tweet hide replies: request body marshal %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPut, tweetHideRepliesEndpoint.urlID(c.Host, id), bytes.NewReader(enc)) if err != nil { - return fmt.Errorf("tweet hide replies request: %w", err) + return nil, fmt.Errorf("tweet hide replies request: %w", err) } req.Header.Add("Accept", "application/json") c.Authorizer.Add(req) resp, err := c.Client.Do(req) if err != nil { - return fmt.Errorf("tweet hide replies response: %w", err) + return nil, fmt.Errorf("tweet hide replies response: %w", err) } defer resp.Body.Close() - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("tweet hide replies response read: %w", err) - } + rl := rateFromHeader(resp.Header) + + decoder := json.NewDecoder(resp.Body) + if resp.StatusCode != http.StatusOK { errResp := &ErrorResponse{} - if err := json.Unmarshal(respBytes, errResp); err != nil { - return &HTTPError{ + if err := decoder.Decode(errResp); err != nil { + return nil, &HTTPError{ Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } errResp.StatusCode = resp.StatusCode - return errResp + errResp.RateLimit = rl + return nil, errResp } - type responseData struct { - Data body `json:"data"` - } - rd := &responseData{} - if err := json.Unmarshal(respBytes, rd); err != nil { - return fmt.Errorf("tweet hide replies response error decode: %w", err) - } - if rd.Data.Hidden != hide { - return fmt.Errorf("tweet hide replies response unable to hide %v", hide) + rd := &TweetHideReplyResponse{} + if err := decoder.Decode(rd); err != nil { + return nil, &ResponseDecodeError{ + Name: "tweet hide replies", + Err: err, + RateLimit: rl, + } } - return nil + rd.RateLimit = rl + return rd, nil } // UserRetweet will retweet a tweet for a user @@ -1229,6 +1435,8 @@ func (c *Client) UserRetweet(ctx context.Context, userID, tweetID string) (*User decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -1236,16 +1444,23 @@ func (c *Client) UserRetweet(ctx context.Context, userID, tweetID string) (*User Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } raw := &UserRetweetResponse{} if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("user retweet decode response %w", err) + return nil, &ResponseDecodeError{ + Name: "user retweet", + Err: err, + RateLimit: rl, + } } + raw.RateLimit = rl return raw, nil } @@ -1275,6 +1490,8 @@ func (c *Client) DeleteUserRetweet(ctx context.Context, userID, tweetID string) decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -1282,16 +1499,23 @@ func (c *Client) DeleteUserRetweet(ctx context.Context, userID, tweetID string) Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } raw := &DeleteUserRetweetResponse{} if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("user delete retweet decode response %w", err) + return nil, &ResponseDecodeError{ + Name: "delete user retweet", + Err: err, + RateLimit: rl, + } } + raw.RateLimit = rl return raw, nil } @@ -1321,6 +1545,8 @@ func (c *Client) UserBlocksLookup(ctx context.Context, userID string, opts UserB } defer resp.Body.Close() + rl := rateFromHeader(resp.Header) + respBytes, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("user blocked lookup response read: %w", err) @@ -1332,9 +1558,11 @@ func (c *Client) UserBlocksLookup(ctx context.Context, userID string, opts UserB Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -1344,13 +1572,21 @@ func (c *Client) UserBlocksLookup(ctx context.Context, userID string, opts UserB } if err := json.Unmarshal(respBytes, blockedLookup.Raw); err != nil { - return nil, fmt.Errorf("user blocked lookup raw response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user blocked lookup", + Err: err, + RateLimit: rl, + } } if err := json.Unmarshal(respBytes, blockedLookup); err != nil { - return nil, fmt.Errorf("user blocked lookup meta response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user blocked lookup", + Err: err, + RateLimit: rl, + } } - + blockedLookup.RateLimit = rl return blockedLookup, nil } @@ -1389,6 +1625,8 @@ func (c *Client) UserBlocks(ctx context.Context, userID, targetUserID string) (* decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -1396,16 +1634,23 @@ func (c *Client) UserBlocks(ctx context.Context, userID, targetUserID string) (* Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } raw := &UserBlocksResponse{} if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("user blocks decode response %w", err) + return nil, &ResponseDecodeError{ + Name: "user blocks", + Err: err, + RateLimit: rl, + } } + raw.RateLimit = rl return raw, nil } @@ -1435,6 +1680,8 @@ func (c *Client) DeleteUserBlocks(ctx context.Context, userID, targetUserID stri decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -1442,16 +1689,23 @@ func (c *Client) DeleteUserBlocks(ctx context.Context, userID, targetUserID stri Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } raw := &UserDeleteBlocksResponse{} if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("user delete blocks decode response %w", err) + return nil, &ResponseDecodeError{ + Name: "delete user blocks", + Err: err, + RateLimit: rl, + } } + raw.RateLimit = rl return raw, nil } @@ -1481,6 +1735,8 @@ func (c *Client) UserMutesLookup(ctx context.Context, userID string, opts UserMu } defer resp.Body.Close() + rl := rateFromHeader(resp.Header) + respBytes, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("user muted lookup response read: %w", err) @@ -1492,9 +1748,11 @@ func (c *Client) UserMutesLookup(ctx context.Context, userID string, opts UserMu Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -1504,13 +1762,21 @@ func (c *Client) UserMutesLookup(ctx context.Context, userID string, opts UserMu } if err := json.Unmarshal(respBytes, mutedLookup.Raw); err != nil { - return nil, fmt.Errorf("user muted lookup raw response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user muted lookup", + Err: err, + RateLimit: rl, + } } if err := json.Unmarshal(respBytes, mutedLookup); err != nil { - return nil, fmt.Errorf("user muted lookup meta response error decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user muted lookup", + Err: err, + RateLimit: rl, + } } - + mutedLookup.RateLimit = rl return mutedLookup, nil } @@ -1549,6 +1815,8 @@ func (c *Client) UserMutes(ctx context.Context, userID, targetUserID string) (*U decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -1556,16 +1824,23 @@ func (c *Client) UserMutes(ctx context.Context, userID, targetUserID string) (*U Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } raw := &UserMutesResponse{} if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("user mutes decode response %w", err) + return nil, &ResponseDecodeError{ + Name: "user mutes", + Err: err, + RateLimit: rl, + } } + raw.RateLimit = rl return raw, nil } @@ -1595,6 +1870,8 @@ func (c *Client) DeleteUserMutes(ctx context.Context, userID, targetUserID strin decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -1602,16 +1879,23 @@ func (c *Client) DeleteUserMutes(ctx context.Context, userID, targetUserID strin Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } raw := &UserDeleteMutesResponse{} if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("user delete mutes decode response %w", err) + return nil, &ResponseDecodeError{ + Name: "user delete mutes", + Err: err, + RateLimit: rl, + } } + raw.RateLimit = rl return raw, nil } @@ -1646,6 +1930,8 @@ func (c *Client) TweetLikesLookup(ctx context.Context, tweetID string, opts Twee decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -1653,9 +1939,11 @@ func (c *Client) TweetLikesLookup(ctx context.Context, tweetID string, opts Twee Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -1665,12 +1953,17 @@ func (c *Client) TweetLikesLookup(ctx context.Context, tweetID string, opts Twee }{} if err := decoder.Decode(&respBody); err != nil { - return nil, fmt.Errorf("tweet user likes lookup dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "tweet likes lookup", + Err: err, + RateLimit: rl, + } } return &TweetLikesLookupResponse{ - Raw: respBody.UserRaw, - Meta: respBody.Meta, + Raw: respBody.UserRaw, + Meta: respBody.Meta, + RateLimit: rl, }, nil } @@ -1705,6 +1998,8 @@ func (c *Client) UserLikesLookup(ctx context.Context, userID string, opts UserLi decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -1712,9 +2007,11 @@ func (c *Client) UserLikesLookup(ctx context.Context, userID string, opts UserLi Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -1724,11 +2021,16 @@ func (c *Client) UserLikesLookup(ctx context.Context, userID string, opts UserLi }{} if err := decoder.Decode(&respBody); err != nil { - return nil, fmt.Errorf("tweet user likes lookup dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "user likes lookup", + Err: err, + RateLimit: rl, + } } return &UserLikesLookupResponse{ - Raw: respBody.TweetRaw, - Meta: respBody.Meta, + Raw: respBody.TweetRaw, + Meta: respBody.Meta, + RateLimit: rl, }, nil } @@ -1767,6 +2069,8 @@ func (c *Client) UserLikes(ctx context.Context, userID, tweetID string) (*UserLi decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -1774,16 +2078,23 @@ func (c *Client) UserLikes(ctx context.Context, userID, tweetID string) (*UserLi Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } raw := &UserLikesResponse{} if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("user likes decode response %w", err) + return nil, &ResponseDecodeError{ + Name: "user likes", + Err: err, + RateLimit: rl, + } } + raw.RateLimit = rl return raw, nil } @@ -1813,6 +2124,8 @@ func (c *Client) DeleteUserLikes(ctx context.Context, userID, tweetID string) (* decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -1820,16 +2133,23 @@ func (c *Client) DeleteUserLikes(ctx context.Context, userID, tweetID string) (* Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } raw := &DeteleUserLikesResponse{} if err := decoder.Decode(raw); err != nil { - return nil, fmt.Errorf("user likes retweet decode response %w", err) + return nil, &ResponseDecodeError{ + Name: "delete user likes", + Err: err, + RateLimit: rl, + } } + raw.RateLimit = rl return raw, nil } @@ -1855,6 +2175,8 @@ func (c *Client) TweetSampleStream(ctx context.Context, opts TweetSampleStreamOp return nil, fmt.Errorf("tweet sample stream response: %w", err) } + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { defer resp.Body.Close() e := &ErrorResponse{} @@ -1863,13 +2185,17 @@ func (c *Client) TweetSampleStream(ctx context.Context, opts TweetSampleStreamOp Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } - return StartTweetStream(resp.Body), nil + stream := StartTweetStream(resp.Body) + stream.RateLimit = rl + return stream, nil } // ListLookup returns the details of a specified list @@ -1898,6 +2224,8 @@ func (c *Client) ListLookup(ctx context.Context, listID string, opts ListLookupO decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -1905,9 +2233,11 @@ func (c *Client) ListLookup(ctx context.Context, listID string, opts ListLookupO Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -1916,11 +2246,16 @@ func (c *Client) ListLookup(ctx context.Context, listID string, opts ListLookupO }{} if err := decoder.Decode(&respBody); err != nil { - return nil, fmt.Errorf("list lookup dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "list lookup", + Err: err, + RateLimit: rl, + } } return &ListLookupResponse{ - Raw: respBody.ListRaw, + Raw: respBody.ListRaw, + RateLimit: rl, }, nil } @@ -1953,6 +2288,8 @@ func (c *Client) UserListLookup(ctx context.Context, userID string, opts UserLis decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -1960,9 +2297,11 @@ func (c *Client) UserListLookup(ctx context.Context, userID string, opts UserLis Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -1972,12 +2311,17 @@ func (c *Client) UserListLookup(ctx context.Context, userID string, opts UserLis }{} if err := decoder.Decode(&respBody); err != nil { - return nil, fmt.Errorf("user list lookup dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "user list lookup", + Err: err, + RateLimit: rl, + } } return &UserListLookupResponse{ - Raw: respBody.UserListRaw, - Meta: respBody.Meta, + Raw: respBody.UserListRaw, + Meta: respBody.Meta, + RateLimit: rl, }, nil } @@ -2010,6 +2354,8 @@ func (c *Client) ListTweetLookup(ctx context.Context, listID string, opts ListTw decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2017,9 +2363,11 @@ func (c *Client) ListTweetLookup(ctx context.Context, listID string, opts ListTw Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -2029,12 +2377,17 @@ func (c *Client) ListTweetLookup(ctx context.Context, listID string, opts ListTw }{} if err := decoder.Decode(&respBody); err != nil { - return nil, fmt.Errorf("list tweet lookup dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "list tweet lookup", + Err: err, + RateLimit: rl, + } } return &ListTweetLookupResponse{ - Raw: respBody.TweetRaw, - Meta: respBody.Meta, + Raw: respBody.TweetRaw, + Meta: respBody.Meta, + RateLimit: rl, }, nil } @@ -2068,6 +2421,8 @@ func (c *Client) CreateList(ctx context.Context, list ListMetaData) (*ListCreate decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusCreated { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2075,18 +2430,24 @@ func (c *Client) CreateList(ctx context.Context, list ListMetaData) (*ListCreate Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } respBody := &ListCreateResponse{} if err := decoder.Decode(respBody); err != nil { - return nil, fmt.Errorf("create list tweet lookup dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "create list", + Err: err, + RateLimit: rl, + } } - + respBody.RateLimit = rl return respBody, nil } @@ -2120,6 +2481,8 @@ func (c *Client) UpdateList(ctx context.Context, listID string, update ListMetaD decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2127,18 +2490,24 @@ func (c *Client) UpdateList(ctx context.Context, listID string, update ListMetaD Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } respBody := &ListUpdateResponse{} if err := decoder.Decode(respBody); err != nil { - return nil, fmt.Errorf("update list tweet lookup dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "update list", + Err: err, + RateLimit: rl, + } } - + respBody.RateLimit = rl return respBody, nil } @@ -2167,6 +2536,8 @@ func (c *Client) DeleteList(ctx context.Context, listID string) (*ListDeleteResp decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2174,18 +2545,24 @@ func (c *Client) DeleteList(ctx context.Context, listID string) (*ListDeleteResp Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } respBody := &ListDeleteResponse{} if err := decoder.Decode(respBody); err != nil { - return nil, fmt.Errorf("delete list tweet lookup dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "delete list", + Err: err, + RateLimit: rl, + } } - + respBody.RateLimit = rl return respBody, nil } @@ -2227,6 +2604,8 @@ func (c *Client) AddListMember(ctx context.Context, listID, userID string) (*Lis decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2234,18 +2613,24 @@ func (c *Client) AddListMember(ctx context.Context, listID, userID string) (*Lis Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } respBody := &ListAddMemberResponse{} if err := decoder.Decode(respBody); err != nil { - return nil, fmt.Errorf("create list tweet lookup dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "add list memeber", + Err: err, + RateLimit: rl, + } } - + respBody.RateLimit = rl return respBody, nil } @@ -2275,6 +2660,8 @@ func (c *Client) RemoveListMember(ctx context.Context, listID, userID string) (* decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2282,18 +2669,24 @@ func (c *Client) RemoveListMember(ctx context.Context, listID, userID string) (* Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } respBody := &ListRemoveMemberResponse{} if err := decoder.Decode(respBody); err != nil { - return nil, fmt.Errorf("remove list tweet lookup dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "remove list memeber", + Err: err, + RateLimit: rl, + } } - + respBody.RateLimit = rl return respBody, nil } @@ -2326,6 +2719,8 @@ func (c *Client) ListUserMembers(ctx context.Context, listID string, opts ListUs decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2333,9 +2728,11 @@ func (c *Client) ListUserMembers(ctx context.Context, listID string, opts ListUs Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -2345,12 +2742,17 @@ func (c *Client) ListUserMembers(ctx context.Context, listID string, opts ListUs }{} if err := decoder.Decode(&respBody); err != nil { - return nil, fmt.Errorf("list user members dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "list user members", + Err: err, + RateLimit: rl, + } } return &ListUserMembersResponse{ - Raw: respBody.UserRaw, - Meta: respBody.Meta, + Raw: respBody.UserRaw, + Meta: respBody.Meta, + RateLimit: rl, }, nil } @@ -2383,6 +2785,8 @@ func (c *Client) UserListMemberships(ctx context.Context, userID string, opts Us decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2390,9 +2794,11 @@ func (c *Client) UserListMemberships(ctx context.Context, userID string, opts Us Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -2402,12 +2808,17 @@ func (c *Client) UserListMemberships(ctx context.Context, userID string, opts Us }{} if err := decoder.Decode(&respBody); err != nil { - return nil, fmt.Errorf("user list membership dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "user list memberships", + Err: err, + RateLimit: rl, + } } return &UserListMembershipsResponse{ - Raw: respBody.UserListMembershipsRaw, - Meta: respBody.Meta, + Raw: respBody.UserListMembershipsRaw, + Meta: respBody.Meta, + RateLimit: rl, }, nil } @@ -2449,6 +2860,8 @@ func (c *Client) UserPinList(ctx context.Context, userID, listID string) (*UserP decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2456,18 +2869,24 @@ func (c *Client) UserPinList(ctx context.Context, userID, listID string) (*UserP Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } respBody := &UserPinListResponse{} if err := decoder.Decode(respBody); err != nil { - return nil, fmt.Errorf("user pin list decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user pin list", + Err: err, + RateLimit: rl, + } } - + respBody.RateLimit = rl return respBody, nil } @@ -2497,6 +2916,8 @@ func (c *Client) UserUnpinList(ctx context.Context, userID, listID string) (*Use decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2504,18 +2925,24 @@ func (c *Client) UserUnpinList(ctx context.Context, userID, listID string) (*Use Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } respBody := &UserUnpinListResponse{} if err := decoder.Decode(respBody); err != nil { - return nil, fmt.Errorf("user unpin list decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user unpin list", + Err: err, + RateLimit: rl, + } } - + respBody.RateLimit = rl return respBody, nil } @@ -2545,6 +2972,8 @@ func (c *Client) UserPinnedLists(ctx context.Context, userID string, opts UserPi decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2552,9 +2981,11 @@ func (c *Client) UserPinnedLists(ctx context.Context, userID string, opts UserPi Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -2564,12 +2995,17 @@ func (c *Client) UserPinnedLists(ctx context.Context, userID string, opts UserPi }{} if err := decoder.Decode(&respBody); err != nil { - return nil, fmt.Errorf("user pinned list dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "user pinned list", + Err: err, + RateLimit: rl, + } } return &UserPinnedListsResponse{ - Raw: respBody.UserPinnedListsRaw, - Meta: respBody.Meta, + Raw: respBody.UserPinnedListsRaw, + Meta: respBody.Meta, + RateLimit: rl, }, nil } @@ -2611,6 +3047,8 @@ func (c *Client) UserFollowList(ctx context.Context, userID, listID string) (*Us decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2618,18 +3056,24 @@ func (c *Client) UserFollowList(ctx context.Context, userID, listID string) (*Us Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } respBody := &UserFollowListResponse{} if err := decoder.Decode(respBody); err != nil { - return nil, fmt.Errorf("user follow list decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user follow list", + Err: err, + RateLimit: rl, + } } - + respBody.RateLimit = rl return respBody, nil } @@ -2659,6 +3103,8 @@ func (c *Client) UserUnfollowList(ctx context.Context, userID, listID string) (* decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2666,18 +3112,24 @@ func (c *Client) UserUnfollowList(ctx context.Context, userID, listID string) (* Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } respBody := &UserUnfollowListResponse{} if err := decoder.Decode(respBody); err != nil { - return nil, fmt.Errorf("user unfollow list decode: %w", err) + return nil, &ResponseDecodeError{ + Name: "user unfollow list", + Err: err, + RateLimit: rl, + } } - + respBody.RateLimit = rl return respBody, nil } @@ -2710,6 +3162,8 @@ func (c *Client) UserFollowedLists(ctx context.Context, userID string, opts User decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2717,9 +3171,11 @@ func (c *Client) UserFollowedLists(ctx context.Context, userID string, opts User Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -2729,12 +3185,17 @@ func (c *Client) UserFollowedLists(ctx context.Context, userID string, opts User }{} if err := decoder.Decode(&respBody); err != nil { - return nil, fmt.Errorf("user followed list dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "user followed list", + Err: err, + RateLimit: rl, + } } return &UserFollowedListsResponse{ - Raw: respBody.UserFollowedListsRaw, - Meta: respBody.Meta, + Raw: respBody.UserFollowedListsRaw, + Meta: respBody.Meta, + RateLimit: rl, }, nil } @@ -2767,6 +3228,8 @@ func (c *Client) ListUserFollowers(ctx context.Context, listID string, opts List decoder := json.NewDecoder(resp.Body) + rl := rateFromHeader(resp.Header) + if resp.StatusCode != http.StatusOK { e := &ErrorResponse{} if err := decoder.Decode(e); err != nil { @@ -2774,9 +3237,11 @@ func (c *Client) ListUserFollowers(ctx context.Context, listID string, opts List Status: resp.Status, StatusCode: resp.StatusCode, URL: resp.Request.URL.String(), + RateLimit: rl, } } e.StatusCode = resp.StatusCode + e.RateLimit = rl return nil, e } @@ -2786,11 +3251,16 @@ func (c *Client) ListUserFollowers(ctx context.Context, listID string, opts List }{} if err := decoder.Decode(&respBody); err != nil { - return nil, fmt.Errorf("list user followers dictionary: %w", err) + return nil, &ResponseDecodeError{ + Name: "list user followers", + Err: err, + RateLimit: rl, + } } return &ListUserFollowersResponse{ - Raw: respBody.UserRaw, - Meta: respBody.Meta, + Raw: respBody.UserRaw, + Meta: respBody.Meta, + RateLimit: rl, }, nil } diff --git a/v2/client_blocks_test.go b/v2/client_blocks_test.go index 08493e6..cf6d21b 100644 --- a/v2/client_blocks_test.go +++ b/v2/client_blocks_test.go @@ -64,6 +64,13 @@ func TestClient_UserBlockedLookup(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -93,6 +100,11 @@ func TestClient_UserBlockedLookup(t *testing.T) { Meta: &UserBlocksLookupMeta{ ResultCount: 3, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -304,6 +316,13 @@ func TestClient_UserBlocks(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -315,6 +334,11 @@ func TestClient_UserBlocks(t *testing.T) { Data: &UserBlocksData{ Blocking: true, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -375,6 +399,13 @@ func TestClient_DeleteUserBlocks(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -386,6 +417,11 @@ func TestClient_DeleteUserBlocks(t *testing.T) { Data: &UserBlocksData{ Blocking: false, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/client_likes_test.go b/v2/client_likes_test.go index a79f10d..dd039e9 100644 --- a/v2/client_likes_test.go +++ b/v2/client_likes_test.go @@ -65,6 +65,13 @@ func TestClient_TweetLikesLookup(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -95,6 +102,11 @@ func TestClient_TweetLikesLookup(t *testing.T) { ResultCount: 2, NextToken: "7140dibdnow9c7btw3w29grvxfcgvpb9n9coehpk7xz5i", }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -312,6 +324,13 @@ func TestClient_UserLikesLookup(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -335,6 +354,11 @@ func TestClient_UserLikesLookup(t *testing.T) { ResultCount: 2, NextToken: "7140dibdnow9c7btw3w29grvxfcgvpb9n9coehpk7xz5i", }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -564,6 +588,13 @@ func TestClient_UserLikes(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -575,6 +606,11 @@ func TestClient_UserLikes(t *testing.T) { Data: &UserLikesData{ Liked: true, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -635,6 +671,13 @@ func TestClient_DeleteUserLikes(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -646,6 +689,11 @@ func TestClient_DeleteUserLikes(t *testing.T) { Data: &UserLikesData{ Liked: false, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/client_list_followers_test.go b/v2/client_list_followers_test.go index 33146f3..a9e182d 100644 --- a/v2/client_list_followers_test.go +++ b/v2/client_list_followers_test.go @@ -47,6 +47,13 @@ func TestClient_UserFollowList(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -58,6 +65,11 @@ func TestClient_UserFollowList(t *testing.T) { List: &UserFollowListData{ Following: true, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -118,6 +130,13 @@ func TestClient_UserUnfollowList(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -129,6 +148,11 @@ func TestClient_UserUnfollowList(t *testing.T) { List: &UserFollowListData{ Following: false, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -195,6 +219,13 @@ func TestClient_UserFollowedLists(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -213,6 +244,11 @@ func TestClient_UserFollowedLists(t *testing.T) { Meta: &UserFollowedListsMeta{ ResultCount: 1, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -358,6 +394,13 @@ func TestClient_ListUserFollowers(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -378,6 +421,11 @@ func TestClient_ListUserFollowers(t *testing.T) { ResultCount: 1, NextToken: "1714209892546977900", }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/client_list_lookup_test.go b/v2/client_list_lookup_test.go index 9bec013..20b0ea2 100644 --- a/v2/client_list_lookup_test.go +++ b/v2/client_list_lookup_test.go @@ -48,6 +48,13 @@ func TestClient_ListLookup(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -61,6 +68,11 @@ func TestClient_ListLookup(t *testing.T) { Name: "Official Twitter Accounts", }, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -191,6 +203,13 @@ func TestClient_UserListLookup(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -209,6 +228,11 @@ func TestClient_UserListLookup(t *testing.T) { Meta: &UserListLookupMeta{ ResultCount: 1, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -351,6 +375,13 @@ func TestClient_ListTweetLookup(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -369,6 +400,11 @@ func TestClient_ListTweetLookup(t *testing.T) { Meta: &ListTweetLookupMeta{ ResultCount: 1, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/client_list_manage_test.go b/v2/client_list_manage_test.go index 7ceb21c..dbd042f 100644 --- a/v2/client_list_manage_test.go +++ b/v2/client_list_manage_test.go @@ -47,6 +47,13 @@ func TestClient_CreateList(t *testing.T) { return &http.Response{ StatusCode: http.StatusCreated, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -63,6 +70,11 @@ func TestClient_CreateList(t *testing.T) { ID: "1441162269824405510", Name: "test v2 create list", }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, }, } @@ -122,6 +134,13 @@ func TestClient_UpdateList(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -138,6 +157,11 @@ func TestClient_UpdateList(t *testing.T) { List: &ListUpdateData{ Updated: true, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, }, } @@ -196,6 +220,13 @@ func TestClient_DeleteList(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -206,6 +237,11 @@ func TestClient_DeleteList(t *testing.T) { List: &ListDeleteData{ Deleted: true, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, }, } diff --git a/v2/client_list_members_test.go b/v2/client_list_members_test.go index e482437..54ac4a4 100644 --- a/v2/client_list_members_test.go +++ b/v2/client_list_members_test.go @@ -47,6 +47,13 @@ func TestClient_AddListMember(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -58,6 +65,11 @@ func TestClient_AddListMember(t *testing.T) { List: &ListMemberData{ Member: true, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, }, } @@ -117,6 +129,13 @@ func TestClient_RemoveListMember(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -128,6 +147,11 @@ func TestClient_RemoveListMember(t *testing.T) { List: &ListMemberData{ Member: false, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, }, } @@ -195,6 +219,13 @@ func TestClient_ListUserMembers(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -215,6 +246,11 @@ func TestClient_ListUserMembers(t *testing.T) { ResultCount: 1, NextToken: "5676935732641845249", }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -355,6 +391,13 @@ func TestClient_UserListMemberships(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -373,6 +416,11 @@ func TestClient_UserListMemberships(t *testing.T) { Meta: &UserListMembershipsMeta{ ResultCount: 1, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/client_mutes_test.go b/v2/client_mutes_test.go index 93e687f..1b9c86d 100644 --- a/v2/client_mutes_test.go +++ b/v2/client_mutes_test.go @@ -64,6 +64,13 @@ func TestClient_UserMutesLookup(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -93,6 +100,11 @@ func TestClient_UserMutesLookup(t *testing.T) { Meta: &UserMutesLookupMeta{ ResultCount: 3, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -304,6 +316,13 @@ func TestClient_UserMutes(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -315,6 +334,11 @@ func TestClient_UserMutes(t *testing.T) { Data: &UserMutesData{ Muting: true, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -375,6 +399,13 @@ func TestClient_DeleteUserMutes(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -386,6 +417,11 @@ func TestClient_DeleteUserMutes(t *testing.T) { Data: &UserMutesData{ Muting: false, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/client_pinned_list_test.go b/v2/client_pinned_list_test.go index 88b5939..06c92fd 100644 --- a/v2/client_pinned_list_test.go +++ b/v2/client_pinned_list_test.go @@ -47,6 +47,13 @@ func TestClient_AddUserPinList(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -58,6 +65,11 @@ func TestClient_AddUserPinList(t *testing.T) { List: &UserPinListData{ Pinned: true, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -118,6 +130,13 @@ func TestClient_RemoveUserPinList(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -129,6 +148,11 @@ func TestClient_RemoveUserPinList(t *testing.T) { List: &UserPinListData{ Pinned: false, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -195,6 +219,13 @@ func TestClient_UserPinnedLists(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -213,6 +244,11 @@ func TestClient_UserPinnedLists(t *testing.T) { Meta: &UserPinnedListsMeta{ ResultCount: 1, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, }, { diff --git a/v2/client_retweet_test.go b/v2/client_retweet_test.go index 623a746..5de3460 100644 --- a/v2/client_retweet_test.go +++ b/v2/client_retweet_test.go @@ -47,6 +47,13 @@ func TestClient_UserRetweet(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -58,6 +65,11 @@ func TestClient_UserRetweet(t *testing.T) { Data: &RetweetData{ Retweeted: true, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -136,6 +148,13 @@ func TestClient_DeleteUserRetweet(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -147,6 +166,11 @@ func TestClient_DeleteUserRetweet(t *testing.T) { Data: &RetweetData{ Retweeted: false, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -242,6 +266,13 @@ func TestClient_UserRetweetLookup(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -271,6 +302,11 @@ func TestClient_UserRetweetLookup(t *testing.T) { Meta: &UserRetweetMeta{ ResultCount: 3, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/client_tweet_hide_replies_test.go b/v2/client_tweet_hide_replies_test.go index 9d4f77c..3e7b3cf 100644 --- a/v2/client_tweet_hide_replies_test.go +++ b/v2/client_tweet_hide_replies_test.go @@ -5,6 +5,7 @@ import ( "io" "log" "net/http" + "reflect" "strings" "testing" ) @@ -23,6 +24,7 @@ func TestClient_TweetHideReplies(t *testing.T) { name string fields fields args args + want *TweetHideReplyResponse wantErr bool }{ { @@ -41,6 +43,13 @@ func TestClient_TweetHideReplies(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -48,6 +57,16 @@ func TestClient_TweetHideReplies(t *testing.T) { id: "63046977", hide: true, }, + want: &TweetHideReplyResponse{ + Reply: &TweetHideReplyData{ + Hidden: true, + }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, + }, wantErr: false, }, } @@ -58,8 +77,13 @@ func TestClient_TweetHideReplies(t *testing.T) { Client: tt.fields.Client, Host: tt.fields.Host, } - if err := c.TweetHideReplies(context.Background(), tt.args.id, tt.args.hide); (err != nil) != tt.wantErr { + got, err := c.TweetHideReplies(context.Background(), tt.args.id, tt.args.hide) + if (err != nil) != tt.wantErr { t.Errorf("Client.TweetHideReplies() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Client.TweetHideReplies() = %v, want %v", got, tt.want) } }) } diff --git a/v2/client_tweet_lookup_test.go b/v2/client_tweet_lookup_test.go index 68a0879..fa613a0 100644 --- a/v2/client_tweet_lookup_test.go +++ b/v2/client_tweet_lookup_test.go @@ -48,6 +48,13 @@ func TestClient_TweetLookup(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -63,6 +70,11 @@ func TestClient_TweetLookup(t *testing.T) { }, }, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/client_tweet_manage_test.go b/v2/client_tweet_manage_test.go index 48cb049..7161ff5 100644 --- a/v2/client_tweet_manage_test.go +++ b/v2/client_tweet_manage_test.go @@ -47,6 +47,13 @@ func TestClient_CreateTweet(t *testing.T) { return &http.Response{ StatusCode: http.StatusCreated, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -60,6 +67,11 @@ func TestClient_CreateTweet(t *testing.T) { Text: "Hello world!", ID: "1445880548472328192", }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -128,6 +140,13 @@ func TestClient_DeleteTweet(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -138,6 +157,11 @@ func TestClient_DeleteTweet(t *testing.T) { Tweet: &DeleteTweetData{ Deleted: true, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/client_tweet_recent_counts_test.go b/v2/client_tweet_recent_counts_test.go index b3a19c8..751f1f3 100644 --- a/v2/client_tweet_recent_counts_test.go +++ b/v2/client_tweet_recent_counts_test.go @@ -60,6 +60,13 @@ func TestClient_TweetRecentCounts(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -82,6 +89,11 @@ func TestClient_TweetRecentCounts(t *testing.T) { Meta: &TweetRecentCountsMeta{ TotalTweetCount: 4, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/client_tweet_search_test.go b/v2/client_tweet_search_test.go index cf4319c..820d124 100644 --- a/v2/client_tweet_search_test.go +++ b/v2/client_tweet_search_test.go @@ -93,6 +93,13 @@ func TestClient_TweetRecentSearch(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -150,6 +157,11 @@ func TestClient_TweetRecentSearch(t *testing.T) { ResultCount: 10, NextToken: "b26v89c19zqg8o3fo7gghep0wmpt92c0wn0jiqwtc7tdp", }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -454,6 +466,13 @@ func TestClient_TweetSearchStreamAddRule(t *testing.T) { return &http.Response{ StatusCode: http.StatusCreated, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -496,6 +515,11 @@ func TestClient_TweetSearchStreamAddRule(t *testing.T) { Created: 2, }, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -560,6 +584,13 @@ func TestClient_TweetSearchStreamDeleteRule(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -580,6 +611,11 @@ func TestClient_TweetSearchStreamDeleteRule(t *testing.T) { Deleted: 2, }, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -651,6 +687,13 @@ func TestClient_TweetSearchStreamRules(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -683,6 +726,11 @@ func TestClient_TweetSearchStreamRules(t *testing.T) { return t }(), }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -747,6 +795,13 @@ func TestClient_TweetSearchStreamDeleteRuleByValue(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -767,6 +822,11 @@ func TestClient_TweetSearchStreamDeleteRuleByValue(t *testing.T) { Deleted: 2, }, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -837,6 +897,13 @@ func TestClient_TweetSearchStream(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(stream)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, diff --git a/v2/client_tweet_stream_test.go b/v2/client_tweet_stream_test.go index 8d7cd44..cc1d46e 100644 --- a/v2/client_tweet_stream_test.go +++ b/v2/client_tweet_stream_test.go @@ -58,6 +58,13 @@ func TestClient_TweetSampleStream(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(stream)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, diff --git a/v2/client_user_followers_test.go b/v2/client_user_followers_test.go index e9a4cc3..eb5b81c 100644 --- a/v2/client_user_followers_test.go +++ b/v2/client_user_followers_test.go @@ -60,6 +60,13 @@ func TestClient_UserFollowersLookup(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -85,6 +92,11 @@ func TestClient_UserFollowersLookup(t *testing.T) { ResultCount: 2, NextToken: "DFEDBNRFT3MHCZZZ", }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/client_user_following_test.go b/v2/client_user_following_test.go index fe563e6..6a8d744 100644 --- a/v2/client_user_following_test.go +++ b/v2/client_user_following_test.go @@ -60,6 +60,13 @@ func TestClient_UserFollowingLookup(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -85,6 +92,11 @@ func TestClient_UserFollowingLookup(t *testing.T) { ResultCount: 2, NextToken: "DFEDBNRFT3MHCZZZ", }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -265,6 +277,13 @@ func TestClient_UserFollows(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -277,6 +296,11 @@ func TestClient_UserFollows(t *testing.T) { Following: true, PendingFollow: true, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -337,6 +361,13 @@ func TestClient_DeleteUserFollows(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -348,6 +379,11 @@ func TestClient_DeleteUserFollows(t *testing.T) { Data: &UserDeleteFollowsData{ Following: false, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/client_user_lookup_test.go b/v2/client_user_lookup_test.go index 23f94b1..72a7537 100644 --- a/v2/client_user_lookup_test.go +++ b/v2/client_user_lookup_test.go @@ -49,6 +49,13 @@ func TestClient_UserLookup(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -65,6 +72,11 @@ func TestClient_UserLookup(t *testing.T) { }, }, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -343,6 +355,13 @@ func TestClient_UserNameLookup(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -359,6 +378,11 @@ func TestClient_UserNameLookup(t *testing.T) { }, }, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, @@ -636,6 +660,13 @@ func TestClient_AuthUserLookup(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -650,6 +681,11 @@ func TestClient_AuthUserLookup(t *testing.T) { }, }, }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/client_user_mention_timeline_test.go b/v2/client_user_mention_timeline_test.go index 14b4e5b..38993a9 100644 --- a/v2/client_user_mention_timeline_test.go +++ b/v2/client_user_mention_timeline_test.go @@ -60,6 +60,13 @@ func TestClient_UserMentionTimeline(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -85,6 +92,11 @@ func TestClient_UserMentionTimeline(t *testing.T) { NewestID: "1338980844036349953", NextToken: "7140dibdnow9c7btw3w29kzu0unnfqs1lzcdi6s0vvj8z", }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/client_user_tweet_timeline_test.go b/v2/client_user_tweet_timeline_test.go index 3f9ff1c..88e2b56 100644 --- a/v2/client_user_tweet_timeline_test.go +++ b/v2/client_user_tweet_timeline_test.go @@ -60,6 +60,13 @@ func TestClient_UserTweetTimeline(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(body)), + Header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), } }), }, @@ -85,6 +92,11 @@ func TestClient_UserTweetTimeline(t *testing.T) { NewestID: "1338971066773905408", NextToken: "7140dibdnow9c7btw3w29grvxfcgvpb9n9coehpk7xz5i", }, + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, }, wantErr: false, }, diff --git a/v2/error_obj.go b/v2/error_obj.go index b05dbb1..e969960 100644 --- a/v2/error_obj.go +++ b/v2/error_obj.go @@ -2,14 +2,31 @@ package twitter import "fmt" +// ResponseDecodeError is an error when a response has a decoding error, JSON. +type ResponseDecodeError struct { + Name string + Err error + RateLimit *RateLimit +} + +func (r *ResponseDecodeError) Error() string { + return fmt.Sprintf("%s decode error: %v", r.Name, r.Err) +} + +// Unwrap will return the wrapped error +func (r *ResponseDecodeError) Unwrap() error { + return r.Err +} + // HTTPError is a response error where the body is not JSON, but XML. This commonly seen in 404 errors. type HTTPError struct { Status string StatusCode int URL string + RateLimit *RateLimit } -func (h *HTTPError) Error() string { +func (h HTTPError) Error() string { return fmt.Sprintf("twitter [%s] status: %s code: %d", h.URL, h.Status, h.StatusCode) } @@ -32,12 +49,13 @@ type Error struct { // ErrorResponse is returned by a non-success callout type ErrorResponse struct { StatusCode int - Errors []Error `json:"errors"` - Title string `json:"title"` - Detail string `json:"detail"` - Type string `json:"type"` + Errors []Error `json:"errors"` + Title string `json:"title"` + Detail string `json:"detail"` + Type string `json:"type"` + RateLimit *RateLimit `json:"-"` } -func (e *ErrorResponse) Error() string { +func (e ErrorResponse) Error() string { return fmt.Sprintf("twitter callout status %d %s:%s", e.StatusCode, e.Title, e.Detail) } diff --git a/v2/list_lookup.go b/v2/list_lookup.go index 62b2259..cd350a2 100644 --- a/v2/list_lookup.go +++ b/v2/list_lookup.go @@ -43,7 +43,8 @@ type ListRawIncludes struct { // ListLookupResponse is the response from the list lookup type ListLookupResponse struct { - Raw *ListRaw + Raw *ListRaw + RateLimit *RateLimit } //UserListLookupOpts are the response field options @@ -86,8 +87,9 @@ type UserListRaw struct { // UserListLookupResponse is the raw ressponse with meta type UserListLookupResponse struct { - Raw *UserListRaw - Meta *UserListLookupMeta `json:"meta"` + Raw *UserListRaw + Meta *UserListLookupMeta `json:"meta"` + RateLimit *RateLimit } // UserListLookupMeta is the meta data for the lists @@ -130,8 +132,9 @@ func (l ListTweetLookupOpts) addQuery(req *http.Request) { // ListTweetLookupResponse is the response to the list tweet lookup type ListTweetLookupResponse struct { - Raw *TweetRaw - Meta *ListTweetLookupMeta `json:"meta"` + Raw *TweetRaw + Meta *ListTweetLookupMeta `json:"meta"` + RateLimit *RateLimit } // ListTweetLookupMeta is the meta data associated with the list tweet lookup @@ -188,8 +191,9 @@ type UserListMembershipsMeta struct { // UserListMembershipsResponse the user list membership response type UserListMembershipsResponse struct { - Raw *UserListMembershipsRaw - Meta *UserListMembershipsMeta `json:"meta"` + Raw *UserListMembershipsRaw + Meta *UserListMembershipsMeta `json:"meta"` + RateLimit *RateLimit } // ListUserMembersOpts is the list user member options @@ -232,8 +236,9 @@ type ListUserMembersMeta struct { // ListUserMembersResponse is the response to the list user members type ListUserMembersResponse struct { - Raw *UserRaw - Meta *ListUserMembersMeta `json:"meta"` + Raw *UserRaw + Meta *ListUserMembersMeta `json:"meta"` + RateLimit *RateLimit } // UserPinnedListsOpts pinned list options @@ -261,8 +266,9 @@ func (l UserPinnedListsOpts) addQuery(req *http.Request) { // UserPinnedListsResponse pinned list response type UserPinnedListsResponse struct { - Raw *UserPinnedListsRaw - Meta *UserPinnedListsMeta `json:"meta"` + Raw *UserPinnedListsRaw + Meta *UserPinnedListsMeta `json:"meta"` + RateLimit *RateLimit } // UserPinnedListsRaw the raw data for pinned lists @@ -310,8 +316,9 @@ func (l UserFollowedListsOpts) addQuery(req *http.Request) { // UserFollowedListsResponse is the user followed response type UserFollowedListsResponse struct { - Raw *UserFollowedListsRaw - Meta *UserFollowedListsMeta `json:"meta"` + Raw *UserFollowedListsRaw + Meta *UserFollowedListsMeta `json:"meta"` + RateLimit *RateLimit } // UserFollowedListsRaw is the raw response for the user followed @@ -368,6 +375,7 @@ type ListUserFollowersMeta struct { // ListUserFollowersResponse is the response for the list followers type ListUserFollowersResponse struct { - Raw *UserRaw - Meta *ListUserFollowersMeta `json:"meta"` + Raw *UserRaw + Meta *ListUserFollowersMeta `json:"meta"` + RateLimit *RateLimit } diff --git a/v2/list_manage.go b/v2/list_manage.go index 6f16bee..45cfdce 100644 --- a/v2/list_manage.go +++ b/v2/list_manage.go @@ -9,7 +9,8 @@ type ListMetaData struct { // ListCreateResponse is the response to creating a list type ListCreateResponse struct { - List *ListCreateData `json:"data"` + List *ListCreateData `json:"data"` + RateLimit *RateLimit } // ListCreateData is the data returned from creating a list @@ -20,7 +21,8 @@ type ListCreateData struct { // ListUpdateResponse is the response to updating a list type ListUpdateResponse struct { - List *ListUpdateData `json:"data"` + List *ListUpdateData `json:"data"` + RateLimit *RateLimit } // ListUpdateData is the data returned from updating a list @@ -30,7 +32,8 @@ type ListUpdateData struct { // ListDeleteResponse is the response to deleting a list type ListDeleteResponse struct { - List *ListDeleteData `json:"data"` + List *ListDeleteData `json:"data"` + RateLimit *RateLimit } // ListDeleteData is the data returned from deleting a list @@ -45,12 +48,14 @@ type ListMemberData struct { // ListAddMemberResponse is the list add member response type ListAddMemberResponse struct { - List *ListMemberData `json:"data"` + List *ListMemberData `json:"data"` + RateLimit *RateLimit } // ListRemoveMemberResponse is the list remove member response type ListRemoveMemberResponse struct { - List *ListMemberData `json:"data"` + List *ListMemberData `json:"data"` + RateLimit *RateLimit } // UserPinListData pinned data @@ -60,12 +65,14 @@ type UserPinListData struct { // UserPinListResponse pin list response type UserPinListResponse struct { - List *UserPinListData `json:"data"` + List *UserPinListData `json:"data"` + RateLimit *RateLimit } // UserUnpinListResponse upin list response type UserUnpinListResponse struct { - List *UserPinListData `json:"data"` + List *UserPinListData `json:"data"` + RateLimit *RateLimit } // UserFollowListData is the list following data @@ -75,10 +82,12 @@ type UserFollowListData struct { // UserFollowListResponse is the user follow response type UserFollowListResponse struct { - List *UserFollowListData `json:"data"` + List *UserFollowListData `json:"data"` + RateLimit *RateLimit } // UserUnfollowListResponse is the user unfollow response type UserUnfollowListResponse struct { - List *UserFollowListData `json:"data"` + List *UserFollowListData `json:"data"` + RateLimit *RateLimit } diff --git a/v2/rate_limits.go b/v2/rate_limits.go new file mode 100644 index 0000000..e0d4db3 --- /dev/null +++ b/v2/rate_limits.go @@ -0,0 +1,66 @@ +package twitter + +import ( + "errors" + "net/http" + "strconv" + "time" +) + +const ( + rateLimit = "x-rate-limit-limit" + rateRemaining = "x-rate-limit-remaining" + rateReset = "x-rate-limit-reset" +) + +// Epoch is the UNIX seconds from 1/1/1970 +type Epoch int + +// Time returns the epoch time +func (e Epoch) Time() time.Time { + return time.Unix(int64(e), 0) +} + +// RateLimit are the rate limit values from the response header +type RateLimit struct { + Limit int + Remaining int + Reset Epoch +} + +func rateFromHeader(header http.Header) *RateLimit { + limit, err := strconv.Atoi(header.Get(rateLimit)) + if err != nil { + return nil + } + remaining, err := strconv.Atoi(header.Get(rateRemaining)) + if err != nil { + return nil + } + reset, err := strconv.Atoi(header.Get(rateReset)) + if err != nil { + return nil + } + return &RateLimit{ + Limit: limit, + Remaining: remaining, + Reset: Epoch(reset), + } +} + +// RateLimitFromError returns the rate limits from an error. If there are not any limits, false is returned. +func RateLimitFromError(err error) (*RateLimit, bool) { + var er *ErrorResponse + var hr *HTTPError + var rde *ResponseDecodeError + switch { + case errors.As(err, &er) && er.RateLimit != nil: + return er.RateLimit, true + case errors.As(err, &hr) && hr.RateLimit != nil: + return hr.RateLimit, true + case errors.As(err, &rde) && rde.RateLimit != nil: + return rde.RateLimit, true + default: + } + return nil, false +} diff --git a/v2/rate_limits_test.go b/v2/rate_limits_test.go new file mode 100644 index 0000000..eeba244 --- /dev/null +++ b/v2/rate_limits_test.go @@ -0,0 +1,148 @@ +package twitter + +import ( + "errors" + "net/http" + "reflect" + "testing" +) + +func Test_rateFromHeader(t *testing.T) { + type args struct { + header http.Header + } + tests := []struct { + name string + args args + want *RateLimit + }{ + { + name: "success", + args: args{ + header: func() http.Header { + h := http.Header{} + h.Add(rateLimit, "15") + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), + }, + want: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, + }, + { + name: "fail", + args: args{ + header: func() http.Header { + h := http.Header{} + h.Add(rateRemaining, "12") + h.Add(rateReset, "1644461060") + return h + }(), + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := rateFromHeader(tt.args.header); !reflect.DeepEqual(got, tt.want) { + t.Errorf("rateFromHeader() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRateLimitFromError(t *testing.T) { + type args struct { + err error + } + tests := []struct { + name string + args args + want *RateLimit + want1 bool + }{ + { + name: "error response", + args: args{ + err: &ErrorResponse{ + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, + }, + }, + want: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, + want1: true, + }, + { + name: "http error", + args: args{ + err: &HTTPError{ + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, + }, + }, + want: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, + want1: true, + }, + { + name: "response decode error", + args: args{ + err: &ResponseDecodeError{ + RateLimit: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, + }, + }, + want: &RateLimit{ + Limit: 15, + Remaining: 12, + Reset: Epoch(1644461060), + }, + want1: true, + }, + { + name: "error", + args: args{ + err: errors.New("hit"), + }, + want: nil, + want1: false, + }, + { + name: "no error", + args: args{}, + want: nil, + want1: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1 := RateLimitFromError(tt.args.err) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("RateLimitFromError() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("RateLimitFromError() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} diff --git a/v2/tweet_counts.go b/v2/tweet_counts.go index 7a7395a..40e265e 100644 --- a/v2/tweet_counts.go +++ b/v2/tweet_counts.go @@ -4,6 +4,7 @@ package twitter type TweetRecentCountsResponse struct { TweetCounts []*TweetCount `json:"data"` Meta *TweetRecentCountsMeta `json:"meta"` + RateLimit *RateLimit } // TweetRecentCountsMeta contains the meta data from the recent counts information diff --git a/v2/tweet_likes.go b/v2/tweet_likes.go index 40bfb81..23b7961 100644 --- a/v2/tweet_likes.go +++ b/v2/tweet_likes.go @@ -8,8 +8,9 @@ import ( // TweetLikesLookupResponse is the user from the tweet likes type TweetLikesLookupResponse struct { - Raw *UserRaw - Meta *TweetLikesMeta `json:"meta"` + Raw *UserRaw + Meta *TweetLikesMeta `json:"meta"` + RateLimit *RateLimit } // TweetLikesMeta is the meta data from the response diff --git a/v2/tweet_manage.go b/v2/tweet_manage.go index 780a4e1..00ff9be 100644 --- a/v2/tweet_manage.go +++ b/v2/tweet_manage.go @@ -90,7 +90,8 @@ type CreateTweetData struct { // CreateTweetResponse is the response returned by the create tweet type CreateTweetResponse struct { - Tweet *CreateTweetData `json:"data"` + Tweet *CreateTweetData `json:"data"` + RateLimit *RateLimit } // DeleteTweetData is the indication of the deletion of tweet @@ -100,5 +101,6 @@ type DeleteTweetData struct { // DeleteTweetResponse is the response returned by the delete tweet type DeleteTweetResponse struct { - Tweet *DeleteTweetData `json:"data"` + Tweet *DeleteTweetData `json:"data"` + RateLimit *RateLimit } diff --git a/v2/tweet_raw.go b/v2/tweet_raw.go index e2f6cdf..d57d06f 100644 --- a/v2/tweet_raw.go +++ b/v2/tweet_raw.go @@ -1,20 +1,34 @@ package twitter +// TweetHideReplyData is the hide reply data +type TweetHideReplyData struct { + Hidden bool `json:"hidden"` +} + +// TweetHideReplyResponse is the response from the hide replies +type TweetHideReplyResponse struct { + Reply *TweetHideReplyData `json:"data"` + RateLimit *RateLimit +} + // TweetLookupResponse contains all of the information from a tweet lookup callout type TweetLookupResponse struct { - Raw *TweetRaw + Raw *TweetRaw + RateLimit *RateLimit } // UserMentionTimelineResponse contains the information from the user mention timelint callout type UserMentionTimelineResponse struct { - Raw *TweetRaw - Meta *UserTimelineMeta `json:"meta"` + Raw *TweetRaw + Meta *UserTimelineMeta `json:"meta"` + RateLimit *RateLimit } // UserTweetTimelineResponse contains the information from the user tweet timeline callout type UserTweetTimelineResponse struct { - Raw *TweetRaw - Meta *UserTimelineMeta `json:"meta"` + Raw *TweetRaw + Meta *UserTimelineMeta `json:"meta"` + RateLimit *RateLimit } // UserTimelineMeta contains the meta data from the timeline callout diff --git a/v2/tweet_search.go b/v2/tweet_search.go index 2b56db0..9dce900 100644 --- a/v2/tweet_search.go +++ b/v2/tweet_search.go @@ -69,8 +69,9 @@ func (t TweetRecentSearchOpts) addQuery(req *http.Request) { // TweetRecentSearchResponse contains all of the information from a tweet recent search type TweetRecentSearchResponse struct { - Raw *TweetRaw - Meta *TweetRecentSearchMeta `json:"meta"` + Raw *TweetRaw + Meta *TweetRecentSearchMeta `json:"meta"` + RateLimit *RateLimit } // TweetRecentSearchMeta contains the recent search information @@ -142,22 +143,25 @@ type TweetSearchStreamRuleEntity struct { // TweetSearchStreamRulesResponse is the response to getting the search rules type TweetSearchStreamRulesResponse struct { - Rules []*TweetSearchStreamRuleEntity `json:"data"` - Meta *TweetSearchStreamRuleMeta `json:"meta"` - Errors []*ErrorObj `json:"errors,omitempty"` + Rules []*TweetSearchStreamRuleEntity `json:"data"` + Meta *TweetSearchStreamRuleMeta `json:"meta"` + Errors []*ErrorObj `json:"errors,omitempty"` + RateLimit *RateLimit } // TweetSearchStreamAddRuleResponse is the response from adding rules type TweetSearchStreamAddRuleResponse struct { - Rules []*TweetSearchStreamRuleEntity `json:"data"` - Meta *TweetSearchStreamRuleMeta `json:"meta"` - Errors []*ErrorObj `json:"errors,omitempty"` + Rules []*TweetSearchStreamRuleEntity `json:"data"` + Meta *TweetSearchStreamRuleMeta `json:"meta"` + Errors []*ErrorObj `json:"errors,omitempty"` + RateLimit *RateLimit } // TweetSearchStreamDeleteRuleResponse is the respnse from deleting rules type TweetSearchStreamDeleteRuleResponse struct { - Meta *TweetSearchStreamRuleMeta `json:"meta"` - Errors []*ErrorObj `json:"errors,omitempty"` + Meta *TweetSearchStreamRuleMeta `json:"meta"` + Errors []*ErrorObj `json:"errors,omitempty"` + RateLimit *RateLimit } // TweetSearchStreamRuleMeta is the meta data object from the request diff --git a/v2/tweet_stream.go b/v2/tweet_stream.go index 91693de..062893c 100644 --- a/v2/tweet_stream.go +++ b/v2/tweet_stream.go @@ -156,12 +156,13 @@ type SystemMessage struct { // TweetStream is the stream handler type TweetStream struct { - tweets chan *TweetMessage - system chan map[SystemMessageType]SystemMessage - close chan bool - err chan error - alive bool - mutex sync.RWMutex + tweets chan *TweetMessage + system chan map[SystemMessageType]SystemMessage + close chan bool + err chan error + alive bool + mutex sync.RWMutex + RateLimit *RateLimit } // StartTweetStream will start the tweet streaming diff --git a/v2/user_blocks.go b/v2/user_blocks.go index 1ac0b61..6bfcabe 100644 --- a/v2/user_blocks.go +++ b/v2/user_blocks.go @@ -39,8 +39,9 @@ func (u UserBlocksLookupOpts) addQuery(req *http.Request) { // UserBlocksLookupResponse is the list of users that are blocked type UserBlocksLookupResponse struct { - Raw *UserRaw - Meta *UserBlocksLookupMeta `json:"meta"` + Raw *UserRaw + Meta *UserBlocksLookupMeta `json:"meta"` + RateLimit *RateLimit } // UserBlocksLookupMeta is the meta associated with the blocked users lookup @@ -57,10 +58,12 @@ type UserBlocksData struct { // UserBlocksResponse is when a user blocks another type UserBlocksResponse struct { - Data *UserBlocksData `json:"data"` + Data *UserBlocksData `json:"data"` + RateLimit *RateLimit } // UserDeleteBlocksResponse is when a user unblocks another type UserDeleteBlocksResponse struct { - Data *UserBlocksData `json:"data"` + Data *UserBlocksData `json:"data"` + RateLimit *RateLimit } diff --git a/v2/user_likes.go b/v2/user_likes.go index 631259b..e170b88 100644 --- a/v2/user_likes.go +++ b/v2/user_likes.go @@ -8,12 +8,14 @@ import ( // UserLikesResponse the response for the user likes type UserLikesResponse struct { - Data *UserLikesData `json:"data"` + Data *UserLikesData `json:"data"` + RateLimit *RateLimit } // DeteleUserLikesResponse the response for the user unlike type DeteleUserLikesResponse struct { - Data *UserLikesData `json:"data"` + Data *UserLikesData `json:"data"` + RateLimit *RateLimit } // UserLikesData is the data from the user like management @@ -23,8 +25,9 @@ type UserLikesData struct { // UserLikesLookupResponse is the tweets from the user likes type UserLikesLookupResponse struct { - Raw *TweetRaw - Meta *UserLikesMeta `json:"meta"` + Raw *TweetRaw + Meta *UserLikesMeta `json:"meta"` + RateLimit *RateLimit } // UserLikesMeta is the meta data from the response diff --git a/v2/user_mutes.go b/v2/user_mutes.go index cc8b0bb..888369a 100644 --- a/v2/user_mutes.go +++ b/v2/user_mutes.go @@ -39,8 +39,9 @@ func (u UserMutesLookupOpts) addQuery(req *http.Request) { // UserMutesLookupResponse is the list of users that are muted type UserMutesLookupResponse struct { - Raw *UserRaw - Meta *UserMutesLookupMeta `json:"meta"` + Raw *UserRaw + Meta *UserMutesLookupMeta `json:"meta"` + RateLimit *RateLimit } // UserMutesLookupMeta is the meta associated with the muted users lookup @@ -57,10 +58,12 @@ type UserMutesData struct { // UserMutesResponse is when a user mutes another type UserMutesResponse struct { - Data *UserMutesData `json:"data"` + Data *UserMutesData `json:"data"` + RateLimit *RateLimit } // UserDeleteMutesResponse is when a user unmutes another type UserDeleteMutesResponse struct { - Data *UserMutesData `json:"data"` + Data *UserMutesData `json:"data"` + RateLimit *RateLimit } diff --git a/v2/user_raw.go b/v2/user_raw.go index 0838343..a6210b5 100644 --- a/v2/user_raw.go +++ b/v2/user_raw.go @@ -2,12 +2,14 @@ package twitter // UserLookupResponse contains all of the information from an user lookup callout type UserLookupResponse struct { - Raw *UserRaw + Raw *UserRaw + RateLimit *RateLimit } // UserFollowsResponse is the response from the follows API type UserFollowsResponse struct { - Data *UserFollowsData `json:"data"` + Data *UserFollowsData `json:"data"` + RateLimit *RateLimit } // UserFollowsData is the data from the follows API @@ -18,7 +20,8 @@ type UserFollowsData struct { // UserDeleteFollowsResponse is the response from the unfollows API type UserDeleteFollowsResponse struct { - Data *UserDeleteFollowsData `json:"data"` + Data *UserDeleteFollowsData `json:"data"` + RateLimit *RateLimit } // UserDeleteFollowsData is the data from the unfollows API @@ -28,8 +31,9 @@ type UserDeleteFollowsData struct { // UserFollowingLookupResponse is the response for the user following API type UserFollowingLookupResponse struct { - Raw *UserRaw - Meta *UserFollowinghMeta `json:"meta"` + Raw *UserRaw + Meta *UserFollowinghMeta `json:"meta"` + RateLimit *RateLimit } // UserFollowinghMeta is the meta data returned by the user following API @@ -41,8 +45,9 @@ type UserFollowinghMeta struct { // UserFollowersLookupResponse is the response for the user followers API type UserFollowersLookupResponse struct { - Raw *UserRaw - Meta *UserFollowershMeta `json:"meta"` + Raw *UserRaw + Meta *UserFollowershMeta `json:"meta"` + RateLimit *RateLimit } // UserFollowershMeta is the meta data returned by the user followers API diff --git a/v2/user_retweet.go b/v2/user_retweet.go index 00de59a..edb4674 100644 --- a/v2/user_retweet.go +++ b/v2/user_retweet.go @@ -12,18 +12,21 @@ type RetweetData struct { // UserRetweetResponse is the response with a user retweet type UserRetweetResponse struct { - Data *RetweetData `json:"data"` + Data *RetweetData `json:"data"` + RateLimit *RateLimit } // DeleteUserRetweetResponse is the response with a user retweet type DeleteUserRetweetResponse struct { - Data *RetweetData `json:"data"` + Data *RetweetData `json:"data"` + RateLimit *RateLimit } // UserRetweetLookupResponse os the response that contains the users type UserRetweetLookupResponse struct { - Raw *UserRetweetRaw - Meta *UserRetweetMeta `json:"meta"` + Raw *UserRetweetRaw + Meta *UserRetweetMeta `json:"meta"` + RateLimit *RateLimit } // UserRetweetMeta is the meta data returned by the retweet user lookup