Skip to content

Commit

Permalink
Feature/issue 54 tweet recent counts (#55)
Browse files Browse the repository at this point in the history
* tweet recent counts initial commit

* added unit tests

* added a example

* update README.md

* added comments

* use enum for granularity

update a example

* replace TweetCountsRaw to []*TweetCount
  • Loading branch information
MasatoraAtarashi authored Oct 17, 2021
1 parent 010a699 commit b6f7281
Show file tree
Hide file tree
Showing 7 changed files with 393 additions and 0 deletions.
6 changes: 6 additions & 0 deletions v2/_examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ This [example](./tweet-lookup) demonstrates the tweets lookup API call.
go run *.go -token=YOUR_API_TOKEN -ids=1261326399320715264,1278347468690915330
```

## Recent Tweet Count
This [example](./tweet-recent-counts) demonstrates the recent tweet counts API call.
```
go run &.go -token=YOUR_API_TOKEN -query=YOUR_SEARCH_QUERY
```

## User Lookup
This [example](./user-lookup) demonstrates the users lookup API call.

Expand Down
55 changes: 55 additions & 0 deletions v2/_examples/tweet-recent-counts/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package main

import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"net/http"

twitter "github.com/g8rswimmer/go-twitter/v2"
)

type authorize struct {
Token string
}

func (a authorize) Add(req *http.Request) {
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", a.Token))
}
func main() {
token := flag.String("token", "", "twitter API token")
query := flag.String("query", "", "twitter query")
flag.Parse()

client := &twitter.Client{
Authorizer: authorize{
Token: *token,
},
Client: http.DefaultClient,
Host: "https://api.twitter.com",
}
opts := twitter.TweetRecentCountsOpts{
Granularity: twitter.GranularityHour,
}

fmt.Println("Callout to tweet recent counts callout")

tweetResponse, err := client.TweetRecentCounts(context.Background(), *query, opts)
if err != nil {
log.Panicf("tweet recent counts error: %v", err)
}

enc, err := json.MarshalIndent(tweetResponse.TweetCounts, "", " ")
if err != nil {
log.Panic(err)
}
fmt.Println(string(enc))

metaBytes, err := json.MarshalIndent(tweetResponse.Meta, "", " ")
if err != nil {
log.Panic(err)
}
fmt.Println(string(metaBytes))
}
57 changes: 57 additions & 0 deletions v2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
userMaxIDs = 100
userMaxNames = 100
tweetRecentSearchQueryLength = 512
tweetRecentCountsQueryLength = 512
)

// Client is used to make twitter v2 API callouts.
Expand Down Expand Up @@ -288,6 +289,62 @@ func (c *Client) TweetRecentSearch(ctx context.Context, query string, opts Tweet
return recentSearch, nil
}

// TweetRecentCounts will return a recent tweet counts based of a query
func (c *Client) TweetRecentCounts(ctx context.Context, query string, opts TweetRecentCountsOpts) (*TweetRecentCountsResponse, error) {
switch {
case len(query) == 0:
return nil, fmt.Errorf("tweet recent counts: a query is required: %w", ErrParameter)
case len(query) > tweetRecentCountsQueryLength:
return nil, fmt.Errorf("tweet recent counts: the query over the length (%d): %w", tweetRecentCountsQueryLength, ErrParameter)
default:
}

req, err := http.NewRequestWithContext(ctx, http.MethodGet, tweetRecentCountsEndpoint.url(c.Host), nil)
if err != nil {
return nil, fmt.Errorf("tweet recent counts request: %w", err)
}
req.Header.Add("Accept", "application/json")
c.Authorizer.Add(req)
opts.addQuery(req)
q := req.URL.Query()
q.Add("query", query)
req.URL.RawQuery = q.Encode()

resp, err := c.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("tweet recent counts response: %w", err)
}
defer resp.Body.Close()

respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("tweet recent counts response read: %w", err)
}
if resp.StatusCode != http.StatusOK {
e := &ErrorResponse{}
if err := json.Unmarshal(respBytes, e); err != nil {
return nil, &HTTPError{
Status: resp.Status,
StatusCode: resp.StatusCode,
URL: resp.Request.URL.String(),
}
}
e.StatusCode = resp.StatusCode
return nil, e
}

recentCounts := &TweetRecentCountsResponse{
TweetCounts: []*TweetCount{},
Meta: &TweetRecentCountsMeta{},
}

if err := json.Unmarshal(respBytes, recentCounts); err != nil {
return nil, fmt.Errorf("tweet recent counts response error decode: %w", err)
}

return recentCounts, nil
}

// UserFollowingLookup will return a user's following users
func (c *Client) UserFollowingLookup(ctx context.Context, id string, opts UserFollowingLookupOpts) (*UserFollowingLookupResponse, error) {
if len(id) == 0 {
Expand Down
206 changes: 206 additions & 0 deletions v2/client_tweet_recent_counts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package twitter

import (
"context"
"io/ioutil"
"log"
"net/http"
"reflect"
"strings"
"testing"
"time"
)

func TestClient_TweetRecentCounts(t *testing.T) {
type fields struct {
Authorizer Authorizer
Client *http.Client
Host string
}
type args struct {
query string
opts TweetRecentCountsOpts
}
tests := []struct {
name string
fields fields
args args
want *TweetRecentCountsResponse
wantErr bool
}{
{
name: "success",
fields: fields{
Authorizer: &mockAuth{},
Host: "https://www.test.com",
Client: mockHTTPClient(func(req *http.Request) *http.Response {
if req.Method != http.MethodGet {
log.Panicf("the method is not correct %s %s", req.Method, http.MethodGet)
}
if strings.Contains(req.URL.String(), string(tweetRecentCountsEndpoint)) == false {
log.Panicf("the url is not correct %s %s", req.URL.String(), tweetRecentCountsEndpoint)
}
body := `{
"data": [
{
"end": "2021-05-27t00:00:00.000z",
"start": "2021-05-26t23:00:00.000z",
"tweet_count": 2
},
{
"end": "2021-05-27t01:00:00.000z",
"start": "2021-05-27t00:00:00.000z",
"tweet_count": 2
}
],
"meta": {
"total_tweet_count": 4
}
}`
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(strings.NewReader(body)),
}
}),
},
args: args{
query: "python",
},
want: &TweetRecentCountsResponse{
TweetCounts: []*TweetCount{
{
End: "2021-05-27t00:00:00.000z",
Start: "2021-05-26t23:00:00.000z",
TweetCount: 2,
},
{
End: "2021-05-27t01:00:00.000z",
Start: "2021-05-27t00:00:00.000z",
TweetCount: 2,
},
},
Meta: &TweetRecentCountsMeta{
TotalTweetCount: 4,
},
},
wantErr: false,
},
{
name: "success-optional",
fields: fields{
Authorizer: &mockAuth{},
Host: "https://www.test.com",
Client: mockHTTPClient(func(req *http.Request) *http.Response {
if req.Method != http.MethodGet {
log.Panicf("the method is not correct %s %s", req.Method, http.MethodGet)
}
if strings.Contains(req.URL.String(), string(tweetRecentCountsEndpoint)) == false {
log.Panicf("the url is not correct %s %s", req.URL.String(), tweetRecentCountsEndpoint)
}
body := `{
"data": [
{
"start": "2021-10-08T15:29:42.000Z",
"end": "2021-10-09T00:00:00.000Z",
"tweet_count": 2
},
{
"start": "2021-10-09T00:00:00.000Z",
"end": "2021-10-09T15:29:33.000Z",
"tweet_count": 2
}
],
"meta": {
"total_tweet_count": 4
}
}`
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(strings.NewReader(body)),
}
}),
},
args: args{
query: "python",
opts: TweetRecentCountsOpts{
StartTime: time.Now().Add(-24 * time.Hour),
Granularity: Granularity("day"),
},
},
want: &TweetRecentCountsResponse{
TweetCounts: []*TweetCount{
{
End: "2021-10-09T00:00:00.000Z",
Start: "2021-10-08T15:29:42.000Z",
TweetCount: 2,
},
{
End: "2021-10-09T15:29:33.000Z",
Start: "2021-10-09T00:00:00.000Z",
TweetCount: 2,
},
},
Meta: &TweetRecentCountsMeta{
TotalTweetCount: 4,
},
},
wantErr: false,
},
{
name: "Bad Request",
fields: fields{
Authorizer: &mockAuth{},
Host: "https://www.test.com",
Client: mockHTTPClient(func(req *http.Request) *http.Response {
if req.Method != http.MethodGet {
log.Panicf("the method is not correct %s %s", req.Method, http.MethodGet)
}
if strings.Contains(req.URL.String(), string(tweetRecentCountsEndpoint)) == false {
log.Panicf("the url is not correct %s %s", req.URL.String(), tweetRecentCountsEndpoint)
}
body := `{
"errors": [
{
"parameters": {
"id": [
"aassd"
]
},
"message": "The id query parameter value [aassd] does not match ^[0-9]{1,19}$"
}
],
"title": "Invalid Request",
"detail": "One or more parameters to your request was invalid.",
"type": "https://api.twitter.com/2/problems/invalid-request"
}`
return &http.Response{
StatusCode: http.StatusBadRequest,
Body: ioutil.NopCloser(strings.NewReader(body)),
}
}),
},
args: args{
query: "nothing",
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Client{
Authorizer: tt.fields.Authorizer,
Client: tt.fields.Client,
Host: tt.fields.Host,
}
got, err := c.TweetRecentCounts(context.Background(), tt.args.query, tt.args.opts)
if (err != nil) != tt.wantErr {
t.Errorf("Client.TweetRecentCounts() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Client.TweetRecentCounts() = %v, want %v", got, tt.want)
}
})
}
}
1 change: 1 addition & 0 deletions v2/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const (
userLookupEndpoint endpoint = "2/users"
userNameLookupEndpoint endpoint = "2/users/by"
tweetRecentSearchEndpoint endpoint = "2/tweets/search/recent"
tweetRecentCountsEndpoint endpoint = "2/tweets/counts/recent"
userFollowingEndpoint endpoint = "2/users/{id}/following"
userFollowersEndpoint endpoint = "2/users/{id}/followers"
userTweetTimelineEndpoint endpoint = "2/users/{id}/tweets"
Expand Down
19 changes: 19 additions & 0 deletions v2/tweet_counts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package twitter

// TweetRecentCountsResponse contains all of the information from a tweet recent counts
type TweetRecentCountsResponse struct {
TweetCounts []*TweetCount `json:"data"`
Meta *TweetRecentCountsMeta `json:"meta"`
}

// TweetRecentCountsMeta contains the meta data from the recent counts information
type TweetRecentCountsMeta struct {
TotalTweetCount int `json:"total_tweet_count"`
}

// TweetCount is the object on the tweet counts endpoints
type TweetCount struct {
Start string `json:"start"`
End string `json:"end"`
TweetCount int `json:"tweet_count"`
}
Loading

0 comments on commit b6f7281

Please sign in to comment.