@@ -64,8 +64,17 @@ type PublicMetrics struct {
64
64
65
65
// Tweet represents the structure for a tweet in the Twitter API response
66
66
type Tweet struct {
67
- ID string `json:"id"`
68
- Text string `json:"text"`
67
+ ID string `json:"id"`
68
+ Text string `json:"text"`
69
+ AuthorID * string `json:"author_id"`
70
+
71
+ AdditionalMetadata AdditionalTweetMetadata
72
+ }
73
+
74
+ // AdditionalTweetMetadata adds additinal metadata to a tweet that isn't directly
75
+ // represented in the Twitter API response
76
+ type AdditionalTweetMetadata struct {
77
+ Author * User
69
78
}
70
79
71
80
// GetUserById makes a request to the Twitter API and returns the user's information
@@ -95,39 +104,42 @@ func (c *Client) GetUserByUsername(ctx context.Context, username string) (*User,
95
104
}
96
105
97
106
// GetUserTweets gets tweets for a given user
98
- //
99
- // todo: Doesn't support paging, so only the most recent ones are returned
100
- func (c * Client ) GetUserTweets (ctx context.Context , userId string , maxResults int ) ([]* Tweet , error ) {
107
+ func (c * Client ) GetUserTweets (ctx context.Context , userId string , maxResults int , nextToken * string ) ([]* Tweet , * string , error ) {
101
108
tracer := metrics .TraceMethodCall (ctx , metricsStructName , "GetUserTweets" )
102
109
defer tracer .End ()
103
110
104
111
url := fmt .Sprintf (baseUrl + "users/" + userId + "/tweets?max_results=%d" , maxResults )
112
+ if nextToken != nil {
113
+ url = fmt .Sprintf ("%s&next_token=%s" , url , * nextToken )
114
+ }
105
115
106
- tweets , err := c .getTweets (ctx , url )
116
+ tweets , nextToken , err := c .getTweets (ctx , url )
107
117
if err != nil {
108
118
tracer .OnError (err )
109
119
}
110
- return tweets , err
120
+ return tweets , nextToken , err
111
121
}
112
122
113
- // SearchRecentUserTweets searches for tweets made by a user within the last 7 days
114
- //
115
- // todo: Doesn't support paging, so only the most recent ones are returned
116
- func (c * Client ) SearchRecentUserTweets (ctx context.Context , userId , searchString string , maxResults int ) ([]* Tweet , error ) {
123
+ // SearchRecentTweets searches for recent tweets within the last 7 days matching
124
+ // a search string.
125
+ func (c * Client ) SearchRecentTweets (ctx context.Context , searchString string , maxResults int , nextToken * string ) ([]* Tweet , * string , error ) {
117
126
tracer := metrics .TraceMethodCall (ctx , metricsStructName , "SearchUserTweets" )
118
127
defer tracer .End ()
119
128
120
129
url := fmt .Sprintf (
121
- baseUrl + "tweets/search/recent?query=%s&max_results=%d" ,
122
- url .QueryEscape (fmt . Sprintf ( "from:%s %s" , userId , searchString ) ),
130
+ baseUrl + "tweets/search/recent?query=%s&expansions=author_id&user.fields=username& max_results=%d" ,
131
+ url .QueryEscape (searchString ),
123
132
maxResults ,
124
133
)
134
+ if nextToken != nil {
135
+ url = fmt .Sprintf ("%s&next_token=%s" , url , * nextToken )
136
+ }
125
137
126
- tweets , err := c .getTweets (ctx , url )
138
+ tweets , nextToken , err := c .getTweets (ctx , url )
127
139
if err != nil {
128
140
tracer .OnError (err )
129
141
}
130
- return tweets , err
142
+ return tweets , nextToken , err
131
143
}
132
144
133
145
func (c * Client ) getUser (ctx context.Context , fromUrl string ) (* User , error ) {
@@ -175,15 +187,15 @@ func (c *Client) getUser(ctx context.Context, fromUrl string) (*User, error) {
175
187
return result .Data , nil
176
188
}
177
189
178
- func (c * Client ) getTweets (ctx context.Context , fromUrl string ) ([]* Tweet , error ) {
190
+ func (c * Client ) getTweets (ctx context.Context , fromUrl string ) ([]* Tweet , * string , error ) {
179
191
bearerToken , err := c .getBearerToken (c .clientId , c .clientSecret )
180
192
if err != nil {
181
- return nil , err
193
+ return nil , nil , err
182
194
}
183
195
184
196
req , err := http .NewRequest ("GET" , fromUrl , nil )
185
197
if err != nil {
186
- return nil , err
198
+ return nil , nil , err
187
199
}
188
200
189
201
req = req .WithContext (ctx )
@@ -192,32 +204,52 @@ func (c *Client) getTweets(ctx context.Context, fromUrl string) ([]*Tweet, error
192
204
193
205
resp , err := c .httpClient .Do (req )
194
206
if err != nil {
195
- return nil , err
207
+ return nil , nil , err
196
208
}
197
209
defer resp .Body .Close ()
198
210
199
211
if resp .StatusCode != http .StatusOK {
200
- return nil , fmt .Errorf ("unexpected http status code: %d" , resp .StatusCode )
212
+ return nil , nil , fmt .Errorf ("unexpected http status code: %d" , resp .StatusCode )
201
213
}
202
214
203
215
var result struct {
204
216
Data []* Tweet `json:"data"`
205
217
Errors []* twitterError `json:"errors"`
218
+ Meta struct {
219
+ NextToken * string `json:"next_token"`
220
+ } `json:"meta"`
221
+ Includes struct {
222
+ Users []User `json:"users"`
223
+ } `json:"includes"`
206
224
}
207
225
208
226
body , err := io .ReadAll (resp .Body )
209
227
if err != nil {
210
- return nil , err
228
+ return nil , nil , err
211
229
}
212
230
213
231
if err := json .Unmarshal (body , & result ); err != nil {
214
- return nil , err
232
+ return nil , nil , err
215
233
}
216
234
217
235
if len (result .Errors ) > 0 {
218
- return nil , result .Errors [0 ].toError ()
236
+ return nil , nil , result .Errors [0 ].toError ()
219
237
}
220
- return result .Data , nil
238
+
239
+ for _ , tweet := range result .Data {
240
+ if tweet .AuthorID == nil {
241
+ continue
242
+ }
243
+
244
+ for _ , user := range result .Includes .Users {
245
+ if user .ID == * tweet .AuthorID {
246
+ tweet .AdditionalMetadata .Author = & user
247
+ break
248
+ }
249
+ }
250
+ }
251
+
252
+ return result .Data , result .Meta .NextToken , nil
221
253
}
222
254
223
255
func (c * Client ) getBearerToken (clientId , clientSecret string ) (string , error ) {
0 commit comments