Skip to content

Commit 4e2f550

Browse files
authored
Initial Twitter client implementation to fetch user metadata (#86)
* Initial Twitter client implementation to fetch user metadata * Use context in Twitter HTTP calls
1 parent 4d2a319 commit 4e2f550

File tree

1 file changed

+126
-0
lines changed

1 file changed

+126
-0
lines changed

pkg/twitter/client.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package twitter
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
10+
"github.com/pkg/errors"
11+
12+
"github.com/code-payments/code-server/pkg/metrics"
13+
)
14+
15+
const (
16+
baseUrl = "https://api.twitter.com/2/"
17+
getUserByIdUrl = baseUrl + "users/"
18+
getUserByUsernameUrl = baseUrl + "users/by/username/"
19+
20+
metricsStructName = "twitter.client"
21+
)
22+
23+
type Client struct {
24+
client *http.Client
25+
apiToken string
26+
}
27+
28+
// NewClient returns a new Twitter client
29+
func NewClient(apiToken string) *Client {
30+
return &Client{
31+
client: http.DefaultClient,
32+
apiToken: apiToken,
33+
}
34+
}
35+
36+
// User represents the structure for a user in the Twitter API response
37+
type User struct {
38+
ID string `json:"id"`
39+
Username string `json:"username"`
40+
Name string `json:"name"`
41+
ProfileImageUrl string `json:"profile_image_url"`
42+
PublicMetrics PublicMetrics `json:"public_metrics"`
43+
}
44+
45+
// PublicMetrics are public metrics for a Twitter user
46+
type PublicMetrics struct {
47+
FollowersCount int `json:"followers_count"`
48+
FollowingCount int `json:"following_count"`
49+
TweetCount int `json:"tweet_count"`
50+
LikeCount int `json:"like_count"`
51+
}
52+
53+
// GetUserById makes a request to the Twitter API and returns the user's information
54+
// by ID
55+
func (c *Client) GetUserById(ctx context.Context, id string) (*User, error) {
56+
tracer := metrics.TraceMethodCall(ctx, metricsStructName, "GetUserById")
57+
defer tracer.End()
58+
59+
user, err := c.getUser(ctx, getUserByIdUrl+id)
60+
if err != nil {
61+
tracer.OnError(err)
62+
}
63+
return user, err
64+
}
65+
66+
// GetUserByUsername makes a request to the Twitter API and returns the user's information
67+
// by username
68+
func (c *Client) GetUserByUsername(ctx context.Context, username string) (*User, error) {
69+
tracer := metrics.TraceMethodCall(ctx, metricsStructName, "GetUserByUsername")
70+
defer tracer.End()
71+
72+
user, err := c.getUser(ctx, getUserByUsernameUrl+username)
73+
if err != nil {
74+
tracer.OnError(err)
75+
}
76+
return user, err
77+
}
78+
79+
func (c *Client) getUser(ctx context.Context, fromUrl string) (*User, error) {
80+
req, err := http.NewRequest("GET", fromUrl+"?user.fields=profile_image_url,public_metrics", nil)
81+
if err != nil {
82+
return nil, err
83+
}
84+
85+
req = req.WithContext(ctx)
86+
87+
req.Header.Add("Authorization", "Bearer "+c.apiToken)
88+
89+
resp, err := c.client.Do(req)
90+
if err != nil {
91+
return nil, err
92+
}
93+
defer resp.Body.Close()
94+
95+
if resp.StatusCode != http.StatusOK {
96+
return nil, fmt.Errorf("unexpected http status code: %d", resp.StatusCode)
97+
}
98+
99+
var result struct {
100+
Data User `json:"data"`
101+
Errors []twitterError `json:"errors"`
102+
}
103+
104+
body, err := io.ReadAll(resp.Body)
105+
if err != nil {
106+
return nil, err
107+
}
108+
109+
if err := json.Unmarshal(body, &result); err != nil {
110+
return nil, err
111+
}
112+
113+
if len(result.Errors) > 0 {
114+
return nil, result.Errors[0].toError()
115+
}
116+
return &result.Data, nil
117+
}
118+
119+
type twitterError struct {
120+
Title string `json:"title"`
121+
Detail string `json:"detail"`
122+
}
123+
124+
func (e *twitterError) toError() error {
125+
return errors.Errorf("%s: %s", e.Title, e.Detail)
126+
}

0 commit comments

Comments
 (0)