2022-05-20 22:34:20 +00:00
|
|
|
package twitter
|
|
|
|
|
2022-05-22 20:11:33 +00:00
|
|
|
//go:generate mockery --recursive --name Getter --output ../generated/mocks
|
|
|
|
|
2022-05-20 22:34:20 +00:00
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2022-05-22 20:11:33 +00:00
|
|
|
"time"
|
2022-05-20 22:34:20 +00:00
|
|
|
)
|
|
|
|
|
2022-05-22 20:11:33 +00:00
|
|
|
// ElonID is the Twitter ID of @elonmusk.
|
|
|
|
const ElonID = "44196397"
|
|
|
|
|
|
|
|
// User represents a Twitter user.
|
2022-05-20 22:34:20 +00:00
|
|
|
type User struct {
|
|
|
|
ID string
|
|
|
|
Name string
|
|
|
|
Username string
|
|
|
|
}
|
|
|
|
|
2022-05-22 20:11:33 +00:00
|
|
|
// Tweet represents a tweet.
|
|
|
|
type Tweet struct {
|
|
|
|
ID string
|
|
|
|
Text string
|
|
|
|
}
|
|
|
|
|
|
|
|
// bearerTokenTransport implements http.RoundTripper.
|
|
|
|
type bearerTokenTransport struct {
|
|
|
|
bearerToken string
|
|
|
|
}
|
|
|
|
|
|
|
|
// RoundTrip sets the Authorization header with the provided bearerToken, and
|
|
|
|
// forwards the request. It will panic if the request includes a body (which
|
|
|
|
// requires special handling).
|
|
|
|
func (t *bearerTokenTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
|
|
if req.Body != nil {
|
|
|
|
panic("bearerTokenTransport does not handle requests with non-nil body")
|
|
|
|
}
|
|
|
|
req2 := cloneRequest(req)
|
|
|
|
req2.Header.Set("Authorization", "Bearer "+t.bearerToken)
|
|
|
|
return http.DefaultTransport.RoundTrip(req2)
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://go.googlesource.com/oauth2/+/f95fa95eaa936d9d87489b15d1d18b97c1ba9c28/transport.go#96
|
|
|
|
func cloneRequest(r *http.Request) *http.Request {
|
|
|
|
// shallow copy of the struct
|
|
|
|
r2 := new(http.Request)
|
|
|
|
*r2 = *r
|
|
|
|
// deep copy of the Header
|
|
|
|
r2.Header = make(http.Header, len(r.Header))
|
|
|
|
for k, s := range r.Header {
|
|
|
|
r2.Header[k] = append([]string(nil), s...)
|
|
|
|
}
|
|
|
|
return r2
|
|
|
|
}
|
|
|
|
|
|
|
|
type Getter interface {
|
|
|
|
Get(string) (*http.Response, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewAPIClient returns a new APIClient.
|
|
|
|
func NewAPIClient(httpclient Getter) *APIClient {
|
2022-05-20 22:34:20 +00:00
|
|
|
return &APIClient{httpclient}
|
|
|
|
}
|
|
|
|
|
2022-05-22 20:11:33 +00:00
|
|
|
// NewAPIClient returns a new APIClient which will authenticate with the
|
|
|
|
// provided bearer token.
|
|
|
|
func NewAPIClientWithBearerToken(bearerToken string) *APIClient {
|
|
|
|
return &APIClient{&http.Client{
|
|
|
|
Timeout: time.Second * 5,
|
|
|
|
Transport: &bearerTokenTransport{bearerToken: bearerToken},
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
// APIClient interacts with the Twitter API V2.
|
2022-05-20 22:34:20 +00:00
|
|
|
type APIClient struct {
|
2022-05-22 20:11:33 +00:00
|
|
|
Getter
|
2022-05-20 22:34:20 +00:00
|
|
|
}
|
|
|
|
|
2022-05-22 20:11:33 +00:00
|
|
|
// GetMe returns the currently authenticated user.
|
2022-05-20 22:34:20 +00:00
|
|
|
func (c *APIClient) GetMe() (*User, error) {
|
|
|
|
type oauthResponse struct {
|
2022-05-22 20:11:33 +00:00
|
|
|
Data *User `json:"data"`
|
2022-05-20 22:34:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := c.Get("https://api.twitter.com/2/users/me")
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error fetching resource: %v", err)
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return nil, fmt.Errorf("error fetching resource: status code %d", resp.StatusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
var oauthResp oauthResponse
|
|
|
|
if err = json.NewDecoder(resp.Body).Decode(&oauthResp); err != nil {
|
|
|
|
return nil, fmt.Errorf("error decoding resource: %v", err)
|
|
|
|
}
|
|
|
|
|
2022-05-22 20:11:33 +00:00
|
|
|
return oauthResp.Data, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetElonTweets returns the latest tweets for a given user.
|
|
|
|
func (c *APIClient) GetTweets(userID string, sinceID string) ([]*Tweet, error) {
|
|
|
|
type oauthResponse struct {
|
|
|
|
Data []*Tweet `json:"data"`
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := c.Get(fmt.Sprintf("https://api.twitter.com/2/users/%s/tweets?since_id=%s", userID, sinceID))
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error fetching resource: %v", err)
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return nil, fmt.Errorf("error fetching resource: status code %d", resp.StatusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
var oauthResp oauthResponse
|
|
|
|
if err = json.NewDecoder(resp.Body).Decode(&oauthResp); err != nil {
|
|
|
|
return nil, fmt.Errorf("error decoding resource: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return oauthResp.Data, nil
|
2022-05-20 22:34:20 +00:00
|
|
|
}
|