Skip to content

Commit

Permalink
added new endpoint (#146)
Browse files Browse the repository at this point in the history
* added new endpoint

* corrected typo

* added the unit tests

* added example

* updated the readme for the example

* linting and naming
  • Loading branch information
g8rswimmer authored May 30, 2022
1 parent 235d0d4 commit a0d8485
Show file tree
Hide file tree
Showing 8 changed files with 519 additions and 88 deletions.
1 change: 1 addition & 0 deletions v2/_examples/tweets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The examples can be run my providing some options, including the authorization t

* [Returns most recent tweets composed by a user](./timeline/user-tweet-timeline/main.go)
* [Returns most recent tweet mentioning a user](./timeline/user-mention-timeline/main.go)
* [Allows you to retrieve a collection of the most recent Tweets and Retweets posted by you and users you follow.](./timeline/user-tweet-reverse-chronological-timeline/main.go)

### [Search Tweets](https://developer.twitter.com/en/docs/twitter-api/tweets/search/introduction)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package main

import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"net/http"

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")
userID := flag.String("user_id", "", "user id")
flag.Parse()

client := &twitter.Client{
Authorizer: authorize{
Token: *token,
},
Client: http.DefaultClient,
Host: "https://api.twitter.com",
}
opts := twitter.UserTweetReverseChronologicalTimelineOpts{
TweetFields: []twitter.TweetField{twitter.TweetFieldCreatedAt, twitter.TweetFieldAuthorID, twitter.TweetFieldConversationID, twitter.TweetFieldPublicMetrics, twitter.TweetFieldContextAnnotations},
UserFields: []twitter.UserField{twitter.UserFieldUserName},
Expansions: []twitter.Expansion{twitter.ExpansionAuthorID},
MaxResults: 5,
}

fmt.Println("Callout to tweet user reverse chronological timeline callout")

timeline, err := client.UserTweetReverseChronologicalTimeline(context.Background(), *userID, opts)
if err != nil {
log.Panicf("user reverse chronological timeline error: %v", err)
}

dictionaries := timeline.Raw.TweetDictionaries()

enc, err := json.MarshalIndent(dictionaries, "", " ")
if err != nil {
log.Panic(err)
}
fmt.Println(string(enc))

metaBytes, err := json.MarshalIndent(timeline.Meta, "", " ")
if err != nil {
log.Panic(err)
}
fmt.Println(string(metaBytes))
}
143 changes: 106 additions & 37 deletions v2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,36 @@ import (
)

const (
tweetMaxIDs = 100
userMaxIDs = 100
spaceMaxIDs = 100
spaceByCreatorMaxIDs = 100
userMaxNames = 100
tweetRecentSearchQueryLength = 512
tweetSearchQueryLength = 1024
tweetRecentCountsQueryLength = 512
tweetAllCountsQueryLength = 1024
userBlocksMaxResults = 1000
userMutesMaxResults = 1000
likesMaxResults = 100
likesMinResults = 10
sampleStreamMaxBackoffMin = 5
userListMaxResults = 100
listTweetMaxResults = 100
userListMembershipMaxResults = 100
listUserMemberMaxResults = 100
userListFollowedMaxResults = 100
listuserFollowersMaxResults = 100
quoteTweetMaxResults = 100
quoteTweetMinResults = 10
tweetBookmarksMaxResults = 100
userTweetTimelineMinResults = 5
userTweetTimelineMaxResults = 100
userMentionTimelineMinResults = 5
userMentionTimelineMaxResults = 100
userRetweetLookupMaxResults = 100
tweetMaxIDs = 100
userMaxIDs = 100
spaceMaxIDs = 100
spaceByCreatorMaxIDs = 100
userMaxNames = 100
tweetRecentSearchQueryLength = 512
tweetSearchQueryLength = 1024
tweetRecentCountsQueryLength = 512
tweetAllCountsQueryLength = 1024
userBlocksMaxResults = 1000
userMutesMaxResults = 1000
likesMaxResults = 100
likesMinResults = 10
sampleStreamMaxBackOffMin = 5
userListMaxResults = 100
listTweetMaxResults = 100
userListMembershipMaxResults = 100
listUserMemberMaxResults = 100
userListFollowedMaxResults = 100
listUserFollowersMaxResults = 100
quoteTweetMaxResults = 100
quoteTweetMinResults = 10
tweetBookmarksMaxResults = 100
userTweetTimelineMinResults = 5
userTweetTimelineMaxResults = 100
userMentionTimelineMinResults = 5
userMentionTimelineMaxResults = 100
userRetweetLookupMaxResults = 100
userTweetReverseChronologicalTimelineMinResults = 1
userTweetReverseChronologicalTimelineMaxResults = 100
)

// Client is used to make twitter v2 API callouts.
Expand Down Expand Up @@ -937,8 +939,8 @@ func (c *Client) TweetSearchStreamRules(ctx context.Context, ruleIDs []TweetSear
func (c *Client) TweetSearchStream(ctx context.Context, opts TweetSearchStreamOpts) (*TweetStream, error) {
switch {
case opts.BackfillMinutes == 0:
case opts.BackfillMinutes > sampleStreamMaxBackoffMin:
return nil, fmt.Errorf("tweet search stream: a max backoff minutes [%d] is [current: %d]: %w", sampleStreamMaxBackoffMin, opts.BackfillMinutes, ErrParameter)
case opts.BackfillMinutes > sampleStreamMaxBackOffMin:
return nil, fmt.Errorf("tweet search stream: a max backoff minutes [%d] is [current: %d]: %w", sampleStreamMaxBackOffMin, opts.BackfillMinutes, ErrParameter)
default:
}

Expand Down Expand Up @@ -1501,6 +1503,73 @@ func (c *Client) UserMentionTimeline(ctx context.Context, userID string, opts Us
return timeline, nil
}

// UserTweetReverseChronologicalTimeline allows you to retrieve a collection of the most recent Tweets and Retweets posted by you and users you follow.
// This endpoint returns up to the last 3200 Tweets.
func (c *Client) UserTweetReverseChronologicalTimeline(ctx context.Context, userID string, opts UserTweetReverseChronologicalTimelineOpts) (*UserTweetReverseChronologicalTimelineResponse, error) {
switch {
case len(userID) == 0:
return nil, fmt.Errorf("user tweet reverse chronological timeline: a query is required: %w", ErrParameter)
case opts.MaxResults == 0:
case opts.MaxResults < userTweetReverseChronologicalTimelineMinResults:
return nil, fmt.Errorf("user tweet reverse chronological timeline: max results [%d] have a min[%d] %w", opts.MaxResults, userTweetReverseChronologicalTimelineMinResults, ErrParameter)
case opts.MaxResults > userTweetReverseChronologicalTimelineMaxResults:
return nil, fmt.Errorf("user tweet reverse chronological timeline: max results [%d] have a max[%d] %w", opts.MaxResults, userTweetReverseChronologicalTimelineMaxResults, ErrParameter)
default:
}

req, err := http.NewRequestWithContext(ctx, http.MethodGet, userTweetReverseChronologicalTimelineEndpoint.urlID(c.Host, userID), nil)
if err != nil {
return nil, fmt.Errorf("user tweet reverse chronological timeline request: %w", err)
}
req.Header.Add("Accept", "application/json")
c.Authorizer.Add(req)
opts.addQuery(req)

resp, err := c.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("user tweet reverse chronological timeline response: %w", err)
}
defer resp.Body.Close()

rl := rateFromHeader(resp.Header)

decoder := json.NewDecoder(resp.Body)

if resp.StatusCode != http.StatusOK {
e := &ErrorResponse{}
if err := decoder.Decode(e); err != nil {
return nil, &HTTPError{
Status: resp.Status,
StatusCode: resp.StatusCode,
URL: resp.Request.URL.String(),
RateLimit: rl,
}
}
e.StatusCode = resp.StatusCode
e.RateLimit = rl
return nil, e
}

timeline := struct {
TweetRaw
Meta UserReverseChronologicalTimelineMeta `json:"meta"`
}{}

if err := decoder.Decode(&timeline); err != nil {
return nil, &ResponseDecodeError{
Name: "user tweet reverse chronological timeline",
Err: err,
RateLimit: rl,
}
}

return &UserTweetReverseChronologicalTimelineResponse{
Raw: &timeline.TweetRaw,
Meta: &timeline.Meta,
RateLimit: rl,
}, nil
}

// TweetHideReplies will hide the replies for a given tweet
func (c Client) TweetHideReplies(ctx context.Context, id string, hide bool) (*TweetHideReplyResponse, error) {
if len(id) == 0 {
Expand Down Expand Up @@ -2318,8 +2387,8 @@ func (c *Client) DeleteUserLikes(ctx context.Context, userID, tweetID string) (*
func (c *Client) TweetSampleStream(ctx context.Context, opts TweetSampleStreamOpts) (*TweetStream, error) {
switch {
case opts.BackfillMinutes == 0:
case opts.BackfillMinutes > sampleStreamMaxBackoffMin:
return nil, fmt.Errorf("tweet sample stream: a max backoff minutes [%d] is [current: %d]: %w", sampleStreamMaxBackoffMin, opts.BackfillMinutes, ErrParameter)
case opts.BackfillMinutes > sampleStreamMaxBackOffMin:
return nil, fmt.Errorf("tweet sample stream: a max backoff minutes [%d] is [current: %d]: %w", sampleStreamMaxBackOffMin, opts.BackfillMinutes, ErrParameter)
default:
}

Expand Down Expand Up @@ -3365,8 +3434,8 @@ func (c *Client) ListUserFollowers(ctx context.Context, listID string, opts List
case len(listID) == 0:
return nil, fmt.Errorf("list user followers: an id is required: %w", ErrParameter)
case opts.MaxResults == 0:
case opts.MaxResults > listuserFollowersMaxResults:
return nil, fmt.Errorf("list user followers: max results [%d] is greater thanmax [%d]: %w", opts.MaxResults, listuserFollowersMaxResults, ErrParameter)
case opts.MaxResults > listUserFollowersMaxResults:
return nil, fmt.Errorf("list user followers: max results [%d] is greater thanmax [%d]: %w", opts.MaxResults, listUserFollowersMaxResults, ErrParameter)
default:
}

Expand Down Expand Up @@ -3774,7 +3843,7 @@ func (c *Client) CreateComplianceBatchJob(ctx context.Context, jobType Complianc
return nil, fmt.Errorf("create compliance batch job request encode: %w", err)
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, complianceJobsEndpiont.url(c.Host), bytes.NewReader(enc))
req, err := http.NewRequestWithContext(ctx, http.MethodPost, complianceJobsEndpoint.url(c.Host), bytes.NewReader(enc))
if err != nil {
return nil, fmt.Errorf("create compliance batch job request: %w", err)
}
Expand Down Expand Up @@ -3831,7 +3900,7 @@ func (c *Client) ComplianceBatchJob(ctx context.Context, id string) (*Compliance
default:
}

ep := complianceJobsEndpiont.url(c.Host) + "/" + id
ep := complianceJobsEndpoint.url(c.Host) + "/" + id
req, err := http.NewRequestWithContext(ctx, http.MethodGet, ep, nil)
if err != nil {
return nil, fmt.Errorf("compliance batch job request: %w", err)
Expand Down Expand Up @@ -3888,7 +3957,7 @@ func (c *Client) ComplianceBatchJobLookup(ctx context.Context, jobType Complianc
default:
}

ep := complianceJobsEndpiont.url(c.Host)
ep := complianceJobsEndpoint.url(c.Host)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, ep, nil)
if err != nil {
return nil, fmt.Errorf("compliance batch job lookup request: %w", err)
Expand Down
12 changes: 6 additions & 6 deletions v2/client_compliance_jobs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ func TestClient_CreateComplianceBatchJob(t *testing.T) {
if req.Method != http.MethodPost {
log.Panicf("the method is not correct %s %s", req.Method, http.MethodPost)
}
if strings.Contains(req.URL.String(), string(complianceJobsEndpiont)) == false {
log.Panicf("the url is not correct %s %s", req.URL.String(), complianceJobsEndpiont)
if strings.Contains(req.URL.String(), string(complianceJobsEndpoint)) == false {
log.Panicf("the url is not correct %s %s", req.URL.String(), complianceJobsEndpoint)
}
body := `{
"data": {
Expand Down Expand Up @@ -140,8 +140,8 @@ func TestClient_ComplianceBatchJob(t *testing.T) {
if req.Method != http.MethodGet {
log.Panicf("the method is not correct %s %s", req.Method, http.MethodGet)
}
if strings.Contains(req.URL.String(), string(complianceJobsEndpiont)+"/job-id") == false {
log.Panicf("the url is not correct %s %s", req.URL.String(), complianceJobsEndpiont)
if strings.Contains(req.URL.String(), string(complianceJobsEndpoint)+"/job-id") == false {
log.Panicf("the url is not correct %s %s", req.URL.String(), complianceJobsEndpoint)
}
body := `{
"data": {
Expand Down Expand Up @@ -243,8 +243,8 @@ func TestClient_ComplianceBatchJobLookup(t *testing.T) {
if req.Method != http.MethodGet {
log.Panicf("the method is not correct %s %s", req.Method, http.MethodGet)
}
if strings.Contains(req.URL.String(), string(complianceJobsEndpiont)) == false {
log.Panicf("the url is not correct %s %s", req.URL.String(), complianceJobsEndpiont)
if strings.Contains(req.URL.String(), string(complianceJobsEndpoint)) == false {
log.Panicf("the url is not correct %s %s", req.URL.String(), complianceJobsEndpoint)
}
body := `{
"data": [
Expand Down
Loading

0 comments on commit a0d8485

Please sign in to comment.