diff --git a/app/widget_maker.go b/app/widget_maker.go index 8e8dae8f..649c8e61 100644 --- a/app/widget_maker.go +++ b/app/widget_maker.go @@ -45,6 +45,7 @@ import ( "github.com/wtfutil/wtf/modules/spotify" "github.com/wtfutil/wtf/modules/spotifyweb" "github.com/wtfutil/wtf/modules/status" + "github.com/wtfutil/wtf/modules/subreddit" "github.com/wtfutil/wtf/modules/textfile" "github.com/wtfutil/wtf/modules/todo" "github.com/wtfutil/wtf/modules/todoist" @@ -210,6 +211,9 @@ func MakeWidget( case "status": settings := status.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = status.NewWidget(app, settings) + case "subreddit": + settings := subreddit.NewSettingsFromYAML(moduleName, moduleConfig, config) + widget = subreddit.NewWidget(app, pages, settings) case "textfile": settings := textfile.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = textfile.NewWidget(app, pages, settings) diff --git a/modules/subreddit/api.go b/modules/subreddit/api.go new file mode 100644 index 00000000..506a84cb --- /dev/null +++ b/modules/subreddit/api.go @@ -0,0 +1,51 @@ +package subreddit + +import ( + "fmt" + "net/http" + + "github.com/wtfutil/wtf/utils" +) + +var rootPage = "https://reddit.com/r/" + +func GetLinks(subreddit string, sortMode string, topTimePeriod string) ([]Link, error) { + url := rootPage + subreddit + "/" + sortMode + ".json" + if sortMode == "top" { + url = url + "?sort=top&t=" + topTimePeriod + } + + request, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + request.Header.Add("User-Agent", "WTF Utility") + + client := &http.Client{} + resp, err := client.Do(request) + + if err != nil { + return nil, err + } + + if resp.StatusCode > 299 { + return nil, fmt.Errorf(resp.Status) + } + var m RedditDocument + err = utils.ParseJson(&m, resp.Body) + + if err != nil { + return nil, err + } + + if len(m.Data.Children) == 0 { + return nil, fmt.Errorf("No links") + } + + var links []Link + for _, l := range m.Data.Children { + links = append(links, l.Data) + } + return links, nil +} diff --git a/modules/subreddit/keyboard.go b/modules/subreddit/keyboard.go new file mode 100644 index 00000000..011aa598 --- /dev/null +++ b/modules/subreddit/keyboard.go @@ -0,0 +1,17 @@ +package subreddit + +import "github.com/gdamore/tcell" + +func (widget *Widget) initializeKeyboardControls() { + widget.InitializeCommonControls(widget.Refresh) + + widget.SetKeyboardChar("j", widget.Next, "Select next item") + widget.SetKeyboardChar("k", widget.Prev, "Select previous item") + widget.SetKeyboardChar("o", widget.openLink, "Open target URL in browser") + widget.SetKeyboardChar("c", widget.openReddit, "Open Reddit comments in browser") + + widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") + widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item") + widget.SetKeyboardKey(tcell.KeyEnter, widget.openReddit, "Open story in browser") + widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") +} diff --git a/modules/subreddit/link.go b/modules/subreddit/link.go new file mode 100644 index 00000000..0b93d6e1 --- /dev/null +++ b/modules/subreddit/link.go @@ -0,0 +1,20 @@ +package subreddit + +type Link struct { + Score int `json:"ups"` + Title string `json:"title"` + ItemURL string `json:"url"` + Permalink string `json:"permalink"` +} + +type RedditDocument struct { + Data Subreddit `json:"data"` +} + +type RedditLinkDocument struct { + Data Link `json:"data"` +} + +type Subreddit struct { + Children []RedditLinkDocument `json:"Children"` +} diff --git a/modules/subreddit/settings.go b/modules/subreddit/settings.go new file mode 100644 index 00000000..63fc68df --- /dev/null +++ b/modules/subreddit/settings.go @@ -0,0 +1,35 @@ +package subreddit + +import ( + "github.com/olebedev/config" + "github.com/wtfutil/wtf/cfg" +) + +const ( + defaultFocusable = true +) + +// Settings contains the settings for the subreddit view +type Settings struct { + common *cfg.Common + + subreddit string `help:"Subreddit to look at" optional:"false"` + numberOfPosts int `help:"Number of posts to show. Default is 10." optional:"true"` + sortOrder string `help:"Sort order for the posts (hot, new, rising, top), default hot" optional:"true"` + topTimePeriod string `help:"If top sort is selected, the time period to show posts from (hour, week, day, month, year, all, default all)"` +} + +// NewSettingsFromYAML creates the settings for this module from a yaml file +func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { + subreddit := ymlConfig.UString("subreddit") + settings := Settings{ + common: cfg.NewCommonSettingsFromModule(name, subreddit, defaultFocusable, ymlConfig, globalConfig), + + numberOfPosts: ymlConfig.UInt("numberOfPosts", 10), + sortOrder: ymlConfig.UString("sortOrder", "hot"), + topTimePeriod: ymlConfig.UString("topTimePeriod", "all"), + subreddit: subreddit, + } + + return &settings +} diff --git a/modules/subreddit/widget.go b/modules/subreddit/widget.go new file mode 100644 index 00000000..d63db14e --- /dev/null +++ b/modules/subreddit/widget.go @@ -0,0 +1,95 @@ +package subreddit + +import ( + "fmt" + + "github.com/rivo/tview" + "github.com/wtfutil/wtf/utils" + "github.com/wtfutil/wtf/view" +) + +type Widget struct { + view.KeyboardWidget + view.ScrollableWidget + + settings *Settings + err error + links []Link +} + +func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) *Widget { + widget := &Widget{ + KeyboardWidget: view.NewKeyboardWidget(app, pages, settings.common), + ScrollableWidget: view.NewScrollableWidget(app, settings.common), + + settings: settings, + } + + widget.SetRenderFunction(widget.Render) + widget.initializeKeyboardControls() + widget.View.SetInputCapture(widget.InputCapture) + + widget.KeyboardWidget.SetView(widget.View) + + return widget + +} + +/* -------------------- Exported Functions -------------------- */ + +func (widget *Widget) Refresh() { + links, err := GetLinks(widget.settings.subreddit, widget.settings.sortOrder, widget.settings.topTimePeriod) + if err != nil { + widget.err = err + widget.links = nil + widget.SetItemCount(0) + } else { + widget.links = links[:widget.settings.numberOfPosts] + widget.SetItemCount(len(widget.links)) + widget.err = nil + } + widget.Render() +} + +func (widget *Widget) Render() { + widget.Redraw(widget.content) +} + +/* -------------------- Unexported Functions -------------------- */ + +func (widget *Widget) content() (string, string, bool) { + title := "/r/" + widget.settings.subreddit + " - " + widget.settings.sortOrder + if widget.err != nil { + return title, widget.err.Error(), true + } + + var content string + for idx, link := range widget.links { + row := fmt.Sprintf( + `[%s]%2d. %s`, + widget.RowColor(idx), + idx+1, + tview.Escape(link.Title), + ) + content += utils.HighlightableHelper(widget.View, row, idx, len(link.Title)) + } + + return title, content, false +} + +func (widget *Widget) openLink() { + sel := widget.GetSelected() + if sel >= 0 && widget.links != nil && sel < len(widget.links) { + story := &widget.links[sel] + utils.OpenFile(story.ItemURL) + } +} + +func (widget *Widget) openReddit() { + sel := widget.GetSelected() + if sel >= 0 && widget.links != nil && sel < len(widget.links) { + story := &widget.links[sel] + fullLink := "http://reddit.com" + story.Permalink + utils.OpenFile(fullLink) + } +}