Repository: sivchari/gotwtr
Pull request #74 (2021-11-20 16:16:27)

Impl spaces buyers


Patch:
diff --git a/client.go b/client.go
index 59dcfc4..245aeb1 100644
--- a/client.go
+++ b/client.go
@@ -34,14 +34,15 @@ type Client interface {
 	LookUpSpaceByID(ctx context.Context, id string, opt ...*SpaceLookUpOption) (*SpaceLookUpByIDResponse, error)
 	LookUpTweets(ctx context.Context, ids []string, opt ...*TweetLookUpOption) (*TweetLookUpResponse, error)
 	LookUpTweetByID(ctx context.Context, id string, opt ...*TweetLookUpOption) (*TweetLookUpByIDResponse, error)
-	RetrieveStreamRules(ctx context.Context, opt ...*RetrieveStreamRulesOption) (*RetrieveStreamRulesResponse, error)
 	LookUpUsers(ctx context.Context, ids []string, opt ...*UserLookUpOption) (*UserLookUpResponse, error)
 	LookUpUserByID(ctx context.Context, id string, opt ...*UserLookUpOption) (*UserLookUpByIDResponse, error)
 	LookUpUserByUserName(ctx context.Context, name string, opt ...*UserLookUpOption) (*UserLookUpByUserNameResponse, error)
 	LookUpUsersByUserNames(ctx context.Context, names []string, opt ...*UserLookUpOption) (*UsersLookUpByUserNamesResponse, error)
 	LookUpUsersWhoLiked(ctx context.Context, tweetID string, opt ...*LookUpUsersWhoLikedOpts) (*LookUpUsersWhoLikedResponse, error)
+	LookUpUsersWhoPurchasedSpaceTicket(ctx context.Context, id string, opt ...*LookUpUsersWhoPurchasedSpaceTicketOption) (*LookUpUsersWhoPurchasedSpaceTicketResponse, error)
 	// PostFollowing(ctx context.Context, id string, tuid string) (*PostFollowingResponse, error)
 	// PostRetweet(ctx context.Context, uid string, tid string) (*PostRetweetResponse, error)
+	RetrieveStreamRules(ctx context.Context, opt ...*RetrieveStreamRulesOption) (*RetrieveStreamRulesResponse, error)
 	RetweetsLookup(ctx context.Context, id string, opt ...*RetweetsLookupOpts) (*RetweetsLookupResponse, error)
 	SampledStream(ctx context.Context, ch chan<- SampledStreamResponse, errCh chan<- error, opt ...*SampledStreamOpts) *StreamResponse
 	// SearchFullArchiveTweets(ctx context.Context, query string, opt ...*TweetSearchOption) (*TweetSearchResponse, error)
@@ -49,8 +50,8 @@ type Client interface {
 	SearchSpaces(ctx context.Context, query string, opt ...*SearchSpacesOption) (*SearchSpacesResponse, error)
 	// UndoFollowing(ctx context.Context, suid string, tuid string) (*UndoFollowingResponse, error)
 	// UndoRetweet(ctx context.Context, id string, stid string) (*UndoRetweetResponse, error)
-	UserMentionTimeline(ctx context.Context, id string, opt ...*UserMentionTimelineOpts) (*UserMentionTimelineResponse, error)
-	UserTweetTimeline(ctx context.Context, id string, opt ...*UserTweetTimelineOpts) (*UserTweetTimelineResponse, error)
+	UserMentionTimeline(ctx context.Context, id string, opt ...*UserMentionTimelineOption) (*UserMentionTimelineResponse, error)
+	UserTweetTimeline(ctx context.Context, id string, opt ...*UserTweetTimelineOption) (*UserTweetTimelineResponse, error)
 }
 
 var _ Client = (*client)(nil)
@@ -112,6 +113,10 @@ func (c *client) LookUpSpaceByID(ctx context.Context, id string, opt ...*SpaceLo
 	return lookUpSpaceByID(ctx, c, id, opt...)
 }
 
+func (c *client) LookUpUsersWhoPurchasedSpaceTicket(ctx context.Context, id string, opt ...*LookUpUsersWhoPurchasedSpaceTicketOption) (*LookUpUsersWhoPurchasedSpaceTicketResponse, error) {
+	return lookUpUsersWhoPurchasedSpaceTicket(ctx, c, id, opt...)
+}
+
 func (c *client) LookUpTweets(ctx context.Context, ids []string, opt ...*TweetLookUpOption) (*TweetLookUpResponse, error) {
 	return lookUpTweets(ctx, c, ids, opt...)
 }
@@ -180,10 +185,10 @@ func (c *client) UndoRetweet(ctx context.Context, id string, stid string) (*Undo
 	return undoRetweet(ctx, c, id, stid)
 }
 
-func (c *client) UserMentionTimeline(ctx context.Context, id string, opt ...*UserMentionTimelineOpts) (*UserMentionTimelineResponse, error) {
+func (c *client) UserMentionTimeline(ctx context.Context, id string, opt ...*UserMentionTimelineOption) (*UserMentionTimelineResponse, error) {
 	return userMentionTimeline(ctx, c, id, opt...)
 }
 
-func (c *client) UserTweetTimeline(ctx context.Context, id string, opt ...*UserTweetTimelineOpts) (*UserTweetTimelineResponse, error) {
+func (c *client) UserTweetTimeline(ctx context.Context, id string, opt ...*UserTweetTimelineOption) (*UserTweetTimelineResponse, error) {
 	return userTweetTimeline(ctx, c, id, opt...)
 }
diff --git a/endpoint.go b/endpoint.go
index 08e93f0..c4971d3 100644
--- a/endpoint.go
+++ b/endpoint.go
@@ -11,6 +11,7 @@ var (
 	sampleStream      = "https://api.twitter.com/2/tweets/sample/stream"
 	spaceDiscover     = "https://api.twitter.com/2/spaces/by/creator_ids"
 	spaceLookUp       = "https://api.twitter.com/2/spaces"
+	spaceLookUpBuyers = "https://api.twitter.com/2/spaces"
 	spacesSearch      = "https://api.twitter.com/2/spaces/search"
 	timeline          = "https://api.twitter.com/2/users"
 	tweetLookUp       = "https://api.twitter.com/2/tweets"
diff --git a/example/space/lookup/main.go b/example/space/lookup/main.go
index 264ccad..67e9598 100644
--- a/example/space/lookup/main.go
+++ b/example/space/lookup/main.go
@@ -28,4 +28,10 @@ func main() {
 	for i, s := range ss.Spaces {
 		fmt.Printf("index: %d, val: %v\n", i, s)
 	}
+
+	str, err := client.LookUpUsersWhoPurchasedSpaceTicket(context.Background(), "spaceid")
+	if err != nil {
+		panic(err)
+	}
+	fmt.Println(str)
 }
diff --git a/space.go b/space.go
index 246ac26..705d7a0 100644
--- a/space.go
+++ b/space.go
@@ -100,3 +100,13 @@ type DiscoverSpacesByUserIDsResponse struct {
 type DiscoverSpacesMeta struct {
 	ResultCount int `json:"result_count"`
 }
+
+type LookUpUsersWhoPurchasedSpaceTicketResponse struct {
+	Users    []*User                                     `json:"data"`
+	Includes *LookUpUsersWhoPurchasedSpaceTicketIncludes `json:"includes,omitempty"`
+	Errors   []*APIResponseError                         `json:"errors,omitempty"`
+}
+
+type LookUpUsersWhoPurchasedSpaceTicketIncludes struct {
+	Tweets []*Tweet
+}
diff --git a/space_lookup.go b/space_lookup.go
index 18e1fe3..39e1150 100644
--- a/space_lookup.go
+++ b/space_lookup.go
@@ -114,3 +114,50 @@ func lookUpSpaceByID(ctx context.Context, c *client, id string, opt ...*SpaceLoo
 
 	return &slr, nil
 }
+
+func lookUpUsersWhoPurchasedSpaceTicket(ctx context.Context, c *client, id string, opt ...*LookUpUsersWhoPurchasedSpaceTicketOption) (*LookUpUsersWhoPurchasedSpaceTicketResponse, error) {
+	if id == "" {
+		return nil, errors.New("look up users who purchased space ticket: id parameter is required")
+	}
+	spaceLookUpUsers := spaceLookUpBuyers + "/" + id + "/buyers"
+
+	req, err := http.NewRequestWithContext(ctx, http.MethodGet, spaceLookUpUsers, nil)
+	if err != nil {
+		return nil, fmt.Errorf("look up users who purchased space ticket: %w", err)
+	}
+
+	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.bearerToken))
+
+	var sopt LookUpUsersWhoPurchasedSpaceTicketOption
+	switch len(opt) {
+	case 0:
+		// do nothing
+	case 1:
+		sopt = *opt[0]
+	default:
+		return nil, errors.New("look up users who purchased space ticket: only one option is allowed")
+	}
+	sopt.addQuery(req)
+
+	resp, err := c.client.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("look up users who purchased space ticket: %w", err)
+	}
+
+	defer resp.Body.Close()
+
+	var str LookUpUsersWhoPurchasedSpaceTicketResponse
+	if err := json.NewDecoder(resp.Body).Decode(&str); err != nil {
+		return nil, fmt.Errorf("look up users who purchased space ticket: %w", err)
+	}
+
+	if resp.StatusCode != http.StatusOK {
+		return nil, &HTTPError{
+			APIName: "look up users who purchased space ticket",
+			Status:  resp.Status,
+			URL:     req.URL.String(),
+		}
+	}
+
+	return &str, nil
+}
diff --git a/space_option.go b/space_option.go
index 744f81c..ae56dcf 100644
--- a/space_option.go
+++ b/space_option.go
@@ -107,3 +107,37 @@ func (s *DiscoverSpacesOption) addQuery(req *http.Request) {
 		req.URL.RawQuery = q.Encode()
 	}
 }
+
+type LookUpUsersWhoPurchasedSpaceTicketOption struct {
+	Expansions  []Expansion
+	MediaFields []MediaField
+	PlaceFields []PlaceField
+	PollFields  []PollField
+	TweetFields []TweetField
+	UserFields  []UserField
+}
+
+func (o *LookUpUsersWhoPurchasedSpaceTicketOption) addQuery(req *http.Request) {
+	q := req.URL.Query()
+	if len(o.Expansions) > 0 {
+		q.Add("expansions", strings.Join(expansionsToString(o.Expansions), ","))
+	}
+	if len(o.MediaFields) > 0 {
+		q.Add("media.fields", strings.Join(mediaFieldsToString(o.MediaFields), ","))
+	}
+	if len(o.PlaceFields) > 0 {
+		q.Add("place.fields", strings.Join(placeFieldsToString(o.PlaceFields), ","))
+	}
+	if len(o.PollFields) > 0 {
+		q.Add("poll.fields", strings.Join(pollFieldsToString(o.PollFields), ","))
+	}
+	if len(o.TweetFields) > 0 {
+		q.Add("tweet.fields", strings.Join(tweetFieldsToString(o.TweetFields), ","))
+	}
+	if len(o.UserFields) > 0 {
+		q.Add("user.fields", strings.Join(userFieldsToString(o.UserFields), ","))
+	}
+	if len(q) > 0 {
+		req.URL.RawQuery = q.Encode()
+	}
+}
diff --git a/timelines.go b/timelines.go
index 8e1c14e..7e748ab 100644
--- a/timelines.go
+++ b/timelines.go
@@ -8,7 +8,7 @@ import (
 	"net/http"
 )
 
-func userTweetTimeline(ctx context.Context, c *client, id string, opt ...*UserTweetTimelineOpts) (*UserTweetTimelineResponse, error) {
+func userTweetTimeline(ctx context.Context, c *client, id string, opt ...*UserTweetTimelineOption) (*UserTweetTimelineResponse, error) {
 	// check id
 	if len(id) == 0 {
 		return nil, errors.New("user tweet timeline: id parameter is required")
@@ -21,7 +21,7 @@ func userTweetTimeline(ctx context.Context, c *client, id string, opt ...*UserTw
 	}
 	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.bearerToken))
 
-	var uopt UserTweetTimelineOpts
+	var uopt UserTweetTimelineOption
 	switch len(opt) {
 	case 0:
 		// do nothing
@@ -53,7 +53,7 @@ func userTweetTimeline(ctx context.Context, c *client, id string, opt ...*UserTw
 	return &timelines, nil
 }
 
-func userMentionTimeline(ctx context.Context, c *client, id string, opt ...*UserMentionTimelineOpts) (*UserMentionTimelineResponse, error) {
+func userMentionTimeline(ctx context.Context, c *client, id string, opt ...*UserMentionTimelineOption) (*UserMentionTimelineResponse, error) {
 	// check id
 	if len(id) == 0 {
 		return nil, errors.New("user mention timeline: id parameter is required")
@@ -66,7 +66,7 @@ func userMentionTimeline(ctx context.Context, c *client, id string, opt ...*User
 	}
 	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.bearerToken))
 
-	var uopt UserMentionTimelineOpts
+	var uopt UserMentionTimelineOption
 	switch len(opt) {
 	case 0:
 		// do nothing
diff --git a/tweet_option.go b/tweet_option.go
index 114f45b..38e15f6 100644
--- a/tweet_option.go
+++ b/tweet_option.go
@@ -109,7 +109,7 @@ func (t TweetSearchOption) addQuery(req *http.Request) {
 	}
 }
 
-type UserTweetTimelineOpts struct {
+type UserTweetTimelineOption struct {
 	Expansions      []Expansion
 	MediaFields     []MediaField
 	PlaceFields     []PlaceField
@@ -125,7 +125,7 @@ type UserTweetTimelineOpts struct {
 	UntilID         string
 }
 
-func (t UserTweetTimelineOpts) addQuery(req *http.Request) {
+func (t UserTweetTimelineOption) addQuery(req *http.Request) {
 	q := req.URL.Query()
 	if len(t.Expansions) > 0 {
 		q.Add("expansions", strings.Join(expansionsToString(t.Expansions), ","))
@@ -171,7 +171,7 @@ func (t UserTweetTimelineOpts) addQuery(req *http.Request) {
 	}
 }
 
-type UserMentionTimelineOpts struct {
+type UserMentionTimelineOption struct {
 	Expansions      []Expansion
 	MediaFields     []MediaField
 	PlaceFields     []PlaceField
@@ -186,7 +186,7 @@ type UserMentionTimelineOpts struct {
 	UntilID         string
 }
 
-func (t UserMentionTimelineOpts) addQuery(req *http.Request) {
+func (t UserMentionTimelineOption) addQuery(req *http.Request) {
 	q := req.URL.Query()
 	if len(t.Expansions) > 0 {
 		q.Add("expansions", strings.Join(expansionsToString(t.Expansions), ","))


Test patch:
diff --git a/discover_spaces_test.go b/discover_spaces_test.go
index 1d0237f..990a6d9 100644
--- a/discover_spaces_test.go
+++ b/discover_spaces_test.go
@@ -69,11 +69,11 @@ func Test_discoverSpacesByUserIDs(t *testing.T) {
 			c := gotwtr.New("test-key", gotwtr.WithHTTPClient(tt.args.client))
 			got, err := c.DiscoverSpacesByUserIDs(tt.args.ctx, tt.args.ids, tt.args.opt...)
 			if (err != nil) != tt.wantErr {
-				t.Errorf("discoverSpacesByUserIDs() error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("DiscoverSpacesByUserIDs() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
 			if diff := cmp.Diff(got, tt.want); diff != "" {
-				t.Errorf("discoverSpacesByUserIDs() mismatch (-want +got):\n%s", diff)
+				t.Errorf("DiscoverSpacesByUserIDs() mismatch (-want +got):\n%s", diff)
 				return
 			}
 		})
diff --git a/space_lookup_test.go b/space_lookup_test.go
index 551d4df..a014ca3 100644
--- a/space_lookup_test.go
+++ b/space_lookup_test.go
@@ -249,3 +249,170 @@ func Test_lookUpSpaceByID(t *testing.T) {
 		})
 	}
 }
+
+func Test_lookUpUsersWhoPurchasedSpaceTicket(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		ctx    context.Context
+		client *http.Client
+		id     string
+		opt    []*gotwtr.LookUpUsersWhoPurchasedSpaceTicketOption
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    *gotwtr.LookUpUsersWhoPurchasedSpaceTicketResponse
+		wantErr bool
+	}{
+		{
+			name: "200 ok users who bought a ticket to a Space",
+			args: args{
+				ctx: context.Background(),
+				client: mockHTTPClient(func(request *http.Request) *http.Response {
+					data := `{
+						"data": [
+							{
+								"id": "2244994945",
+								"username": "Twtr",
+								"name": "Twtr"
+							},
+							{
+								"id": "783214",
+								"username": "Twitter",
+								"name": "Twitter"
+							}
+						]
+					}`
+					return &http.Response{
+						StatusCode: http.StatusOK,
+						Body:       io.NopCloser(strings.NewReader(data)),
+					}
+				}),
+				id:  "1DXxyRYNejbKM",
+				opt: []*gotwtr.LookUpUsersWhoPurchasedSpaceTicketOption{},
+			},
+			want: &gotwtr.LookUpUsersWhoPurchasedSpaceTicketResponse{
+				Users: []*gotwtr.User{
+					{
+						ID:       "2244994945",
+						UserName: "Twtr",
+						Name:     "Twtr",
+					},
+					{
+						ID:       "783214",
+						UserName: "Twitter",
+						Name:     "Twitter",
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "200 ok users who bought a ticket to a Space with optional fields",
+			args: args{
+				ctx: context.Background(),
+				client: mockHTTPClient(func(request *http.Request) *http.Response {
+					data := `{
+						"data": [
+							{
+								"created_at": "2013-12-14T04:35:55.000Z",
+								"username": "TwitterProd",
+								"pinned_tweet_id": "1255542774432063488",
+								"id": "2244994945",
+								"name": "Twitter Prod"
+							},
+							{
+								"created_at": "2007-02-20T14:35:54.000Z",
+								"username": "Twitter",
+								"pinned_tweet_id": "1274087687469715457",
+								"id": "783214",
+								"name": "Twitter"
+							}
+						],
+						"includes": {
+							"tweets": [
+								{
+									"created_at": "2020-04-29T17:01:38.000Z",
+									"text": "During these unprecedented times, what’s happening on Twitter can help the world better understand &amp; respond to the pandemic. nnWe're launching a free COVID-19 stream endpoint so qualified devs &amp; researchers can study the public conversation in real-time. https://t.co/BPqMcQzhId",
+									"id": "1255542774432063488"
+								},
+								{
+									"created_at": "2020-06-19T21:12:30.000Z",
+									"text": "📍 Minneapolisn🗣️ @FredTJoseph https://t.co/lNTOkyguG1",
+									"id": "1274087687469715457"
+								}
+							]
+						}
+					}`
+					return &http.Response{
+						StatusCode: http.StatusOK,
+						Body:       io.NopCloser(strings.NewReader(data)),
+					}
+				}),
+				id: "1DXxyRYNejbKM",
+				opt: []*gotwtr.LookUpUsersWhoPurchasedSpaceTicketOption{
+					{
+						Expansions: []gotwtr.Expansion{
+							gotwtr.ExpansionPinnedTweetID,
+						},
+						UserFields: []gotwtr.UserField{
+							gotwtr.UserFieldCreatedAt,
+						},
+						TweetFields: []gotwtr.TweetField{
+							gotwtr.TweetFieldCreatedAt,
+						},
+					},
+				},
+			},
+			want: &gotwtr.LookUpUsersWhoPurchasedSpaceTicketResponse{
+				Users: []*gotwtr.User{
+					{
+						ID:            "2244994945",
+						UserName:      "TwitterProd",
+						Name:          "Twitter Prod",
+						CreatedAt:     "2013-12-14T04:35:55.000Z",
+						PinnedTweetID: "1255542774432063488",
+					},
+					{
+						ID:            "783214",
+						UserName:      "Twitter",
+						Name:          "Twitter",
+						CreatedAt:     "2007-02-20T14:35:54.000Z",
+						PinnedTweetID: "1274087687469715457",
+					},
+				},
+				Includes: &gotwtr.LookUpUsersWhoPurchasedSpaceTicketIncludes{
+					Tweets: []*gotwtr.Tweet{
+						{
+							ID:        "1255542774432063488",
+							CreatedAt: "2020-04-29T17:01:38.000Z",
+							Text:      "During these unprecedented times, what’s happening on Twitter can help the world better understand &amp; respond to the pandemic. nnWe're launching a free COVID-19 stream endpoint so qualified devs &amp; researchers can study the public conversation in real-time. https://t.co/BPqMcQzhId",
+						},
+						{
+							ID:        "1274087687469715457",
+							CreatedAt: "2020-06-19T21:12:30.000Z",
+							Text:      "📍 Minneapolisn🗣️ @FredTJoseph https://t.co/lNTOkyguG1",
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+	}
+	for i, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			c := gotwtr.New("test-key", gotwtr.WithHTTPClient(tt.args.client))
+			got, err := c.LookUpUsersWhoPurchasedSpaceTicket(tt.args.ctx, tt.args.id, tt.args.opt...)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("LookUpSpaceByID() index = %v error = %v, wantErr %v", i, err, tt.wantErr)
+				return
+			}
+			if diff := cmp.Diff(got, tt.want); diff != "" {
+				t.Errorf("discoverSpacesByUserIDs() mismatch (-want +got):\n%s", diff)
+				return
+			}
+		})
+	}
+}
diff --git a/timelines_test.go b/timelines_test.go
index cb72dc0..b1f27ed 100644
--- a/timelines_test.go
+++ b/timelines_test.go
@@ -16,7 +16,7 @@ func Test_userTweetTimeline(t *testing.T) {
 		ctx    context.Context
 		client *http.Client
 		id     string
-		opt    []*gotwtr.UserTweetTimelineOpts
+		opt    []*gotwtr.UserTweetTimelineOption
 	}
 	tests := []struct {
 		name    string
@@ -156,7 +156,7 @@ func Test_userTweetTimeline(t *testing.T) {
 					}
 				}),
 				id: "2244994945",
-				opt: []*gotwtr.UserTweetTimelineOpts{
+				opt: []*gotwtr.UserTweetTimelineOption{
 					{
 						TweetFields: []gotwtr.TweetField{
 							gotwtr.TweetFieldAttachments,
@@ -279,7 +279,7 @@ func Test_userMentionTimeline(t *testing.T) {
 		ctx    context.Context
 		client *http.Client
 		id     string
-		opt    []*gotwtr.UserMentionTimelineOpts
+		opt    []*gotwtr.UserMentionTimelineOption
 	}
 	tests := []struct {
 		name    string
@@ -515,7 +515,7 @@ func Test_userMentionTimeline(t *testing.T) {
 					}
 				}),
 				id: "2244994945",
-				opt: []*gotwtr.UserMentionTimelineOpts{
+				opt: []*gotwtr.UserMentionTimelineOption{
 					{
 						TweetFields: []gotwtr.TweetField{
 							gotwtr.TweetFieldAttachments,