package twitterstats import ( "context" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "golang.org/x/oauth2" "golang.org/x/oauth2/clientcredentials" ) // Client contains state that allows stats to be fetched about a list of Twitter users type Client struct { httpClient *http.Client screenNames []string } // TwitterStats Represents a stats snapshot for a single Twitter user at a point in time type TwitterStats struct { FollowerCount int64 `json:"followers_count"` TweetCount int64 `json:"statuses_count"` } const ( userTimelineURL = "https://api.twitter.com/1.1/users/show.json" ) // NewClient creates a new twitterstats client that contains an OAuth2 HTTP client which can be used func NewClient(settings *Settings) *Client { usernames := make([]string, len(settings.screenNames)) for i, username := range settings.screenNames { var ok bool if usernames[i], ok = username.(string); !ok { log.Fatalf("All `screenName`s in twitterstats config must be of type string") } } var httpClient *http.Client // If a bearer token is supplied, use that directly. Otherwise, let the Oauth client fetch a token // using the consumer key and secret. if settings.bearerToken == "" { conf := &clientcredentials.Config{ ClientID: settings.consumerKey, ClientSecret: settings.consumerSecret, TokenURL: "https://api.twitter.com/oauth2/token", } httpClient = conf.Client(context.Background()) } else { ctx := context.Background() httpClient = oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{ AccessToken: settings.bearerToken, TokenType: "Bearer", })) } client := Client{ httpClient: httpClient, screenNames: usernames, } return &client } // GetStatsForUser Fetches stats for a single user. If there is an error fetching or parsing the response // from the Twitter API, an empty stats struct will be returned. func (client *Client) GetStatsForUser(username string) TwitterStats { stats := TwitterStats{ FollowerCount: 0, TweetCount: 0, } url := fmt.Sprintf("%s?screen_name=%s", userTimelineURL, username) resp, err := client.httpClient.Get(url) if err != nil { return stats } defer func() { _ = resp.Body.Close() }() body, err := ioutil.ReadAll(resp.Body) if err != nil { return stats } // If there is an error while parsing, just discard the error and return the empty stats err = json.Unmarshal(body, &stats) if err != nil { return stats } return stats } // GetStats Returns a slice of `TwitterStats` structs for each username in `client.screenNames` in the same // order of `client.screenNames` func (client *Client) GetStats() []TwitterStats { stats := make([]TwitterStats, len(client.screenNames)) for i, username := range client.screenNames { stats[i] = client.GetStatsForUser(username) } return stats }