diff --git a/main.go b/main.go index 2145e1fa..9a3a90cf 100644 --- a/main.go +++ b/main.go @@ -43,6 +43,7 @@ import ( "github.com/senorprogrammer/wtf/todoist" "github.com/senorprogrammer/wtf/travisci" "github.com/senorprogrammer/wtf/trello" + "github.com/senorprogrammer/wtf/twitter" "github.com/senorprogrammer/wtf/weatherservices/prettyweather" "github.com/senorprogrammer/wtf/weatherservices/weather" "github.com/senorprogrammer/wtf/wtf" @@ -233,6 +234,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { widgets = append(widgets, travisci.NewWidget()) case "trello": widgets = append(widgets, trello.NewWidget()) + case "twitter": + widgets = append(widgets, twitter.NewWidget()) case "weather": widgets = append(widgets, weather.NewWidget(app, pages)) case "zendesk": diff --git a/twitter/client.go b/twitter/client.go new file mode 100644 index 00000000..b9c45d46 --- /dev/null +++ b/twitter/client.go @@ -0,0 +1,64 @@ +package twitter + +import ( + "encoding/json" + "fmt" + "os" + "strconv" +) + +/* NOTE: Currently single application ONLY +* bearer tokens are only supported for applications, not single-users + */ + +// Client represents the data required to connect to the Twitter API +type Client struct { + apiBase string + bearerToken string + count int + screenName string +} + +// NewClient creates and returns a new Twitter client +func NewClient(url string) *Client { + client := Client{ + apiBase: url, + screenName: "wtfutil", + count: 5, + bearerToken: os.Getenv("WTF_TWITTER_BEARER_TOKEN"), + } + + return &client +} + +/* -------------------- Public Functions -------------------- */ + +// Tweets returns a list of tweets of a user +func (client *Client) Tweets() []Tweet { + tweets, err := client.tweets() + if err != nil { + return []Tweet{} + } + + return tweets +} + +/* -------------------- Private Functions -------------------- */ + +// tweets is the private interface for retrieving the list of user sweets +func (client *Client) tweets() (tweets []Tweet, err error) { + apiURL := fmt.Sprintf( + "%s/statuses/user_timeline.json?screen_name=%s&count=%s", + client.apiBase, + client.screenName, + strconv.Itoa(client.count), + ) + + data, err := Request(client.bearerToken, apiURL) + if err != nil { + return tweets, err + } + err = json.Unmarshal(data, &tweets) + + return +} diff --git a/twitter/request.go b/twitter/request.go new file mode 100644 index 00000000..39565b5d --- /dev/null +++ b/twitter/request.go @@ -0,0 +1,41 @@ +package twitter + +import ( + "bytes" + "fmt" + "net/http" +) + +func Request(bearerToken string, apiURL string) ([]byte, error) { + req, err := http.NewRequest("GET", apiURL, nil) + if err != nil { + return nil, err + } + + req.Header.Add("Authorization", + fmt.Sprintf("Bearer %s", bearerToken)) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + data, err := ParseBody(resp) + if err != nil { + return nil, err + } + + return data, err +} + +func ParseBody(resp *http.Response) ([]byte, error) { + var buffer bytes.Buffer + _, err := buffer.ReadFrom(resp.Body) + if err != nil { + return nil, err + } + + return buffer.Bytes(), nil +} diff --git a/twitter/tweet.go b/twitter/tweet.go new file mode 100644 index 00000000..18dc1ea9 --- /dev/null +++ b/twitter/tweet.go @@ -0,0 +1,26 @@ +package twitter + +import ( + "fmt" + + "github.com/senorprogrammer/wtf/wtf" +) + +type Tweet struct { + User User `json:"user"` + Text string `json:"text"` + CreatedAt string `json:"created_at"` +} + +func (tweet *Tweet) String() string { + return fmt.Sprintf("Tweet: %s at %s by %s", tweet.Text, tweet.CreatedAt, tweet.User.ScreenName) +} + +/* -------------------- Exported Functions -------------------- */ + +func (tweet *Tweet) Username() string { + return fmt.Sprint(tweet.User.ScreenName) +} +func (tweet *Tweet) PrettyStart() string { + return wtf.PrettyDate(tweet.CreatedAt) +} diff --git a/twitter/user.go b/twitter/user.go new file mode 100644 index 00000000..f51cdd44 --- /dev/null +++ b/twitter/user.go @@ -0,0 +1,6 @@ +package twitter + +// User is used as part of the Tweet struct to get user information +type User struct { + ScreenName string `json:"screen_name"` +} diff --git a/twitter/widget.go b/twitter/widget.go new file mode 100644 index 00000000..48f72e26 --- /dev/null +++ b/twitter/widget.go @@ -0,0 +1,50 @@ +package twitter + +import ( + "fmt" + + "github.com/senorprogrammer/wtf/wtf" +) + +type Widget struct { + wtf.TextWidget +} + +func NewWidget() *Widget { + widget := Widget{ + TextWidget: wtf.NewTextWidget("Twitter", "twitter", false), + } + + return &widget +} + +/* -------------------- Exported Functions -------------------- */ + +func (widget *Widget) Refresh() { + client := NewClient("https://api.twitter.com/1.1/") + userTweets := client.Tweets() + + widget.UpdateRefreshedAt() + widget.View.SetTitle(widget.ContextualTitle(widget.Name)) + + widget.View.SetText(widget.contentFrom(userTweets)) +} + +/* -------------------- Unexported Functions -------------------- */ + +func (widget *Widget) contentFrom(tweets []Tweet) string { + if len(tweets) == 0 { + return fmt.Sprintf("\n\n\n\n\n\n\n\n%s", wtf.CenterText("[grey]No Tweets[white]", 50)) + } + + str := "" + for _, tweet := range tweets { + str = str + widget.format(tweet) + } + + return str +} + +func (widget *Widget) format(tweet Tweet) string { + return fmt.Sprintf(" [white]%s[grey]\n [grey]%s - %s[white]\n\n", tweet.Text, tweet.Username(), tweet.CreatedAt) +}