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) } } }