diff --git a/hmhm.go b/hmhm.go new file mode 100644 index 0000000..facdcfb --- /dev/null +++ b/hmhm.go @@ -0,0 +1,441 @@ +package main + +import ( + "encoding/json" + "fmt" + termui "github.com/gizak/termui/v3" + "github.com/gizak/termui/v3/widgets" + "io/ioutil" + "log" + // "math" + "net/http" + //"sort" + "os/exec" + "runtime" + "strconv" + "sync" + "time" +) + +type HnStories struct { + Stories []HnItem + Items []int + List []string +} +type HnItem struct { + By string `json:"by,omitempty"` + Descendants int `json:"descendants,omitempty"` + Id int `json:"id,omitempty"` + Kids []int `json:"kids,omitempty"` + Score int `json:"score,omitempty"` + Added int `json:"time,omitempty"` + Title string `json:"title,omitempty"` + Type string `json:"type,omitempty"` + Url string `json:"url,omitempty"` +} + +type LStories struct { + Stories []LobsterItem + List []string +} + +type LobsterItem struct { + ShortID string `json:"short_id"` + ShortIDURL string `json:"short_id_url"` + CreatedAt string `json:"created_at"` + Title string `json:"title"` + Url string `json:"url"` + Score int `json:"score"` + Upvotes int `json:"upvotes"` + Downvotes int `json:"downvotes"` + CommentCount int `json:"comment_count"` + Description string `json:"description"` + CommentsURL string `json:"comments_url"` + SubmitterUser struct { + Username string `json:"username"` + CreatedAt string `json:"created_at"` + IsAdmin bool `json:"is_admin"` + About string `json:"about"` + IsModerator bool `json:"is_moderator"` + Karma int `json:"karma"` + AvatarURL string `json:"avatar_url"` + InvitedByUser string `json:"invited_by_user"` + } `json:"submitter_user"` + Tags []string `json:"tags"` +} + +const apiUrlHN string = "https://hacker-news.firebaseio.com/v0" +const apiUrlL string = "https://lobste.rs/hottest.json" +const mpRSS string = "https://blog.acolyer.org/feed/" +const hnumstories int = 30 +const lnumstories int = 25 +const refreshrate time.Duration = 300 + +func (l *LStories) fetchLStories() { + timeout := time.Duration(10 * time.Second) + client := &http.Client{ + Timeout: timeout, + } + + request, err := http.NewRequest("GET", apiUrlL, nil) + if err != nil { + log.Println(err) + } + response, err := client.Do(request) + if err != nil { + log.Println(err) + } + storydata, err := ioutil.ReadAll(response.Body) + if err != nil { + log.Println(err) + } + + json.Unmarshal(storydata, &l.Stories) +} + +func (h *HnStories) fetchHNStories(item string, wg *sync.WaitGroup) { + defer wg.Done() + var itemUrl string + itemUrl = apiUrlHN + "/item/" + item + ".json" + timeout := time.Duration(10 * time.Second) + client := &http.Client{ + Timeout: timeout, + } + request, err := http.NewRequest("GET", itemUrl, nil) + if err != nil { + log.Println(err) + } + response, err := client.Do(request) + if err != nil { + log.Println(err) + } + storydata, err := ioutil.ReadAll(response.Body) + if err != nil { + log.Println(err) + } + + var story HnItem + + json.Unmarshal(storydata, &story) + + h.Stories = append(h.Stories, HnItem{ + By: story.By, + Id: story.Id, + Added: story.Added, + Title: story.Title, + Type: story.Type, + Url: story.Url, + Kids: story.Kids, + }) +} + +func (h *HnStories) httpHNFetchIds(done chan bool) { + timeout := time.Duration(5 * time.Second) + client := &http.Client{ + Timeout: timeout, + } + request, err := http.NewRequest("GET", apiUrlHN+"/topstories.json", nil) + if err != nil { + log.Println(err) + } + response, err := client.Do(request) + if err != nil { + log.Println(err) + } + d := json.NewDecoder(response.Body) + err = d.Decode(&h.Items) + if err != nil { + log.Println(err) + } + done <- true +} + +func (h *HnStories) httpHNDisplayStories(wg *sync.WaitGroup) { + wg.Add(1) + var counter int = 1 + for _, item := range h.Stories[:hnumstories] { + itemstring := fmt.Sprintf("%-6s %-6s | %d comments", "["+strconv.Itoa(counter)+".](fg:white,bold)", "["+item.Title+"](fg:green)", len(item.Kids)) + h.List = append(h.List, itemstring) + counter++ + } +} +func (l *LStories) listLStories() { + var counter int = 1 + for _, item := range l.Stories[:lnumstories] { + itemstring := fmt.Sprintf("%-6s %-6s | %d comments", "["+strconv.Itoa(counter)+".](fg:white,bold)", "["+item.Title+"](fg:green)", item.CommentCount) + l.List = append(l.List, itemstring) + counter++ + } +} + +func (h *HnStories) populateHNStories(gauge *widgets.Gauge, wg *sync.WaitGroup) { + done := make(chan bool) + go h.httpHNFetchIds(done) + <-done + var counter int = 1 + for _, item := range h.Items[:hnumstories] { + wg.Add(1) + go h.fetchHNStories(strconv.Itoa(item), wg) + fcounter := float64(counter) + fnumstories := float64(hnumstories) + percentage := (fcounter / fnumstories) * 100 + gauge.Percent = int(percentage) + termui.Render(gauge) + counter++ + } + wg.Wait() + go h.httpHNDisplayStories(wg) + wg.Wait() +} +func (h *HnStories) clearHNStruct() { + h = nil +} +func (l *LStories) clearLStruct() { + l = nil +} + +func main() { + if err := termui.Init(); err != nil { + log.Fatalf("failed to initialize termui: %v", err) + } + defer termui.Close() + x, y := termui.TerminalDimensions() + tabpane := widgets.NewTabPane("[Y] Hacker News", "[L] Lobste.rs") + tabpane.SetRect(0, 0, x, 1) + tabpane.Border = false + g0 := widgets.NewGauge() + g0.Title = "Fetching Stories.." + g0.SetRect(0, 3, x, 6) + g0.BarColor = termui.ColorYellow + g0.LabelStyle = termui.NewStyle(termui.ColorBlue) + g0.BorderStyle.Fg = termui.ColorWhite + g0.Percent = 1 + termui.Render(g0) + var h HnStories + var l LStories + var wg sync.WaitGroup + l.fetchLStories() + l.listLStories() + h.populateHNStories(g0, &wg) + termui.Clear() + hlist := widgets.NewList() + hlist.Border = false + x, y = termui.TerminalDimensions() + hlist.SetRect(0, 1, x, y) + hlist.TextStyle = termui.NewStyle(termui.ColorYellow) + hlist.WrapText = true + hlist.Rows = h.List + + llist := widgets.NewList() + llist.Border = false + x, y = termui.TerminalDimensions() + llist.SetRect(0, 1, x, y) + llist.TextStyle = termui.NewStyle(termui.ColorYellow) + llist.WrapText = true + llist.Rows = l.List + renderTab := func() { + switch tabpane.ActiveTabIndex { + case 0: + termui.Render(hlist) + case 1: + termui.Render(llist) + } + } + termui.Render(tabpane, hlist) + + ticker := time.NewTicker(refreshrate * time.Second).C + previousKey := "" + uiEvents := termui.PollEvents() + for { + select { + case e := <-uiEvents: + switch e.ID { + case "h": + tabpane.FocusLeft() + termui.Clear() + termui.Render(tabpane) + renderTab() + case "l": + tabpane.FocusRight() + termui.Clear() + termui.Render(tabpane) + renderTab() + case "q", "": + return + case "r": + termui.Clear() + if tabpane.ActiveTabIndex == 0 { + h.clearHNStruct() + g0.Title = "Refreshing HN Stories.." + g0.Percent = 1 + var wg2 sync.WaitGroup + h.populateHNStories(g0, &wg2) + hlist := widgets.NewList() + hlist.Border = false + x, y = termui.TerminalDimensions() + hlist.SetRect(0, 1, x, y) + hlist.TextStyle = termui.NewStyle(termui.ColorYellow) + hlist.WrapText = true + hlist.Rows = h.List + } else if tabpane.ActiveTabIndex == 1 { + l.clearLStruct() + g0.Title = "Refreshing Lobster Stories.." + g0.Percent = 100 + termui.Render(g0) + l.fetchLStories() + l.listLStories() + llist := widgets.NewList() + llist.Border = false + x, y = termui.TerminalDimensions() + llist.SetRect(0, 1, x, y) + llist.TextStyle = termui.NewStyle(termui.ColorYellow) + llist.WrapText = true + llist.Rows = l.List + } + case "j", "": + if tabpane.ActiveTabIndex == 0 { + hlist.ScrollDown() + } + if tabpane.ActiveTabIndex == 1 { + llist.ScrollDown() + } + case "k", "": + if tabpane.ActiveTabIndex == 0 { + hlist.ScrollUp() + } + if tabpane.ActiveTabIndex == 1 { + llist.ScrollUp() + } + case "": + if tabpane.ActiveTabIndex == 0 { + hlist.ScrollHalfPageDown() + } + if tabpane.ActiveTabIndex == 1 { + llist.ScrollHalfPageDown() + } + case "": + if tabpane.ActiveTabIndex == 0 { + hlist.ScrollHalfPageUp() + } + if tabpane.ActiveTabIndex == 1 { + llist.ScrollHalfPageUp() + } + case "": + if tabpane.ActiveTabIndex == 0 { + hlist.ScrollPageDown() + } + if tabpane.ActiveTabIndex == 1 { + llist.ScrollPageDown() + } + case "": + if tabpane.ActiveTabIndex == 0 { + hlist.ScrollPageUp() + } + if tabpane.ActiveTabIndex == 1 { + llist.ScrollPageUp() + } + case "g": + if tabpane.ActiveTabIndex == 0 { + hlist.ScrollDown() + } + if tabpane.ActiveTabIndex == 1 { + llist.ScrollDown() + } + if previousKey == "g" { + if tabpane.ActiveTabIndex == 0 { + hlist.ScrollTop() + } else if tabpane.ActiveTabIndex == 1 { + llist.ScrollTop() + } + } + case "": + if tabpane.ActiveTabIndex == 0 { + hlist.ScrollTop() + } + if tabpane.ActiveTabIndex == 1 { + llist.ScrollTop() + } + case "G", "": + if tabpane.ActiveTabIndex == 0 { + hlist.ScrollBottom() + } + if tabpane.ActiveTabIndex == 1 { + llist.ScrollBottom() + } + case "": + if tabpane.ActiveTabIndex == 0 { + url := h.Stories[hlist.SelectedRow].Url + openbrowser(url) + } else if tabpane.ActiveTabIndex == 1 { + url := l.Stories[llist.SelectedRow].Url + openbrowser(url) + } + case "": + payload := e.Payload.(termui.Resize) + hlist.SetRect(0, 1, payload.Width, payload.Height) + termui.Clear() + termui.Render(tabpane, hlist, llist) + } + + if previousKey == "g" { + previousKey = "" + } else { + previousKey = e.ID + } + if tabpane.ActiveTabIndex == 0 { + termui.Render(tabpane, hlist) + } else if tabpane.ActiveTabIndex == 1 { + termui.Render(tabpane, llist) + } + case <-ticker: + termui.Clear() + if tabpane.ActiveTabIndex == 0 { + h.clearHNStruct() + g0.Title = "Refreshing HN Stories.." + g0.Percent = 1 + var wg2 sync.WaitGroup + h.populateHNStories(g0, &wg2) + hlist := widgets.NewList() + hlist.Border = false + x, y = termui.TerminalDimensions() + hlist.SetRect(0, 1, x, y) + hlist.TextStyle = termui.NewStyle(termui.ColorYellow) + hlist.WrapText = true + hlist.Rows = h.List + termui.Render(tabpane, hlist) + } else if tabpane.ActiveTabIndex == 1 { + l.clearLStruct() + g0.Title = "Refreshing Lobster Stories.." + g0.Percent = 100 + termui.Render(g0) + l.fetchLStories() + l.listLStories() + llist := widgets.NewList() + llist.Border = false + x, y = termui.TerminalDimensions() + llist.SetRect(0, 1, x, y) + llist.TextStyle = termui.NewStyle(termui.ColorYellow) + llist.WrapText = true + llist.Rows = l.List + termui.Render(tabpane, llist) + } + } + } +} + +func openbrowser(url string) { + var err error + switch runtime.GOOS { + case "linux", "freebsd": + err = exec.Command("xdg-open", url).Start() + case "windows": + err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() + case "darwin": + err = exec.Command("open", "-a", "Safari", url).Start() + default: + err = fmt.Errorf("unsupported platform") + } + if err != nil { + log.Fatal(err) + } +} diff --git a/hummhumm.go b/hummhumm.go deleted file mode 100644 index 021681f..0000000 --- a/hummhumm.go +++ /dev/null @@ -1,229 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - termui "github.com/gizak/termui/v3" - "github.com/gizak/termui/v3/widgets" - "io/ioutil" - "log" - // "math" - "net/http" - //"sort" - "os/exec" - "strconv" - "sync" - "time" -) - -type HnStories struct { - Stories []HnItem - Items []int -} -type HnItem struct { - By string `json:"by,omitempty"` - Descendants int `json:"descendants,omitempty"` - Id int `json:"id,omitempty"` - Kids []int `json:"kids,omitempty"` - Score int `json:"score,omitempty"` - Added int `json:"time,omitempty"` - Title string `json:"title,omitempty"` - Type string `json:"type,omitempty"` - Url string `json:"url,omitempty"` -} - -const apiUrl string = "https://hacker-news.firebaseio.com/v0" -const numstories int = 30 - -func (h *HnStories) fetchHNStories(item string, wg *sync.WaitGroup) { - defer wg.Done() - var itemUrl string - itemUrl = apiUrl + "/item/" + item + ".json" - timeout := time.Duration(5 * time.Second) - client := &http.Client{ - Timeout: timeout, - } - request, err := http.NewRequest("GET", itemUrl, nil) - if err != nil { - log.Println(err) - } - response, err := client.Do(request) - if err != nil { - log.Println(err) - } - storydata, err := ioutil.ReadAll(response.Body) - if err != nil { - log.Println(err) - } - - var story HnItem - - json.Unmarshal(storydata, &story) - - h.Stories = append(h.Stories, HnItem{ - By: story.By, - Id: story.Id, - Added: story.Added, - Title: story.Title, - Type: story.Type, - Url: story.Url, - Kids: story.Kids, - }) -} - -func (h *HnStories) httpHNFetchIds(url string, done chan bool) { - timeout := time.Duration(5 * time.Second) - client := &http.Client{ - Timeout: timeout, - } - request, err := http.NewRequest("GET", url, nil) - if err != nil { - log.Println(err) - } - response, err := client.Do(request) - if err != nil { - log.Println(err) - } - d := json.NewDecoder(response.Body) - err = d.Decode(&h.Items) - if err != nil { - log.Println(err) - } - done <- true -} - -func (h *HnStories) httpHNDisplayStories(numstories int) []string { - counter := 1 - var termuiitems []string - for _, item := range h.Stories[:numstories] { - itemstring := fmt.Sprintf("%-6s %-6s | %d comments", "["+strconv.Itoa(counter)+".](fg:white,bold)", "["+item.Title+"](fg:green)", len(item.Kids)) - termuiitems = append(termuiitems, itemstring) - counter++ - } - return termuiitems -} - -func main() { - if err := termui.Init(); err != nil { - log.Fatalf("failed to initialize termui: %v", err) - } - defer termui.Close() - x, y := termui.TerminalDimensions() - tabpane := widgets.NewTabPane("[Y] Hacker News", "[L] Lobste.rs") - tabpane.SetRect(0, 0, x, 1) - tabpane.Border = false - g0 := widgets.NewGauge() - g0.Title = "Fetching Stories.." - g0.SetRect(0, 3, x, 6) - g0.BarColor = termui.ColorYellow - g0.LabelStyle = termui.NewStyle(termui.ColorBlue) - g0.BorderStyle.Fg = termui.ColorWhite - hnurl := apiUrl + "/topstories.json" - var a HnStories - var counter = 1 - var wg sync.WaitGroup - done1 := make(chan bool, 10) - g0.Percent = 1 - termui.Render(g0) - go a.httpHNFetchIds(hnurl, done1) - <-done1 - for _, item := range a.Items[:numstories] { - wg.Add(1) - go a.fetchHNStories(strconv.Itoa(item), &wg) - fcounter := float64(counter) - fnumstories := float64(numstories) - percentage := (fcounter / fnumstories) * 100 - g0.Percent = int(percentage) - termui.Render(g0) - counter++ - } - wg.Wait() - termui.Clear() - l := widgets.NewList() - chanitems := make(chan []string, numstories) - var items []string - //l.Title = "[Y] hacker news" - l.Border = false - x, y = termui.TerminalDimensions() - l.SetRect(0, 1, x, y) - l.TextStyle = termui.NewStyle(termui.ColorYellow) - l.WrapText = true - //sort.SliceStable(a.Stories, func(i, j int) bool { - // return a.Stories[i].Score > a.Stories[j].Score - //}) - go func(items []string) { - chanitems <- a.httpHNDisplayStories(numstories) - }(items) - select { - case items := <-chanitems: - l.Rows = items - renderTab := func() { - switch tabpane.ActiveTabIndex { - case 0: - termui.Render(tabpane, l) - } - } - termui.Render(tabpane, l) - - previousKey := "" - uiEvents := termui.PollEvents() - for { - e := <-uiEvents - switch e.ID { - case "h": - tabpane.FocusLeft() - termui.Clear() - termui.Render(tabpane, l) - renderTab() - case "l": - tabpane.FocusRight() - termui.Clear() - termui.Render(tabpane, l) - renderTab() - case "q", "": - return - case "r": - - case "j", "": - l.ScrollDown() - case "k", "": - l.ScrollUp() - case "": - l.ScrollHalfPageDown() - case "": - l.ScrollHalfPageUp() - case "": - l.ScrollPageDown() - case "": - l.ScrollPageUp() - case "g": - if previousKey == "g" { - l.ScrollTop() - } - case "": - l.ScrollTop() - case "G", "": - l.ScrollBottom() - case "": - url := a.Stories[l.SelectedRow].Url - err := exec.Command("open", "-a", "Safari", url).Start() - if err != nil { - log.Fatal(err) - } - case "": - payload := e.Payload.(termui.Resize) - l.SetRect(0, 1, payload.Width, payload.Height) - termui.Clear() - termui.Render(l) - } - - if previousKey == "g" { - previousKey = "" - } else { - previousKey = e.ID - } - - termui.Render(l, tabpane) - } - } -}