This repository has been archived on 2022-05-25. You can view files and clone it, but cannot push or open issues or pull requests.
elon-eats-my-tweets/twitter/api.go

126 lines
3.3 KiB
Go

package twitter
//go:generate mockery --recursive --name Getter --output ../generated/mocks
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
// ElonID is the Twitter ID of @elonmusk.
const ElonID = "44196397"
// User represents a Twitter user.
type User struct {
ID string
Name string
Username string
}
// 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 {
return &APIClient{httpclient}
}
// 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.
type APIClient struct {
Getter
}
// GetMe returns the currently authenticated user.
func (c *APIClient) GetMe() (*User, error) {
type oauthResponse struct {
Data *User `json:"data"`
}
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)
}
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
}