211 lines
4.8 KiB
Go
211 lines
4.8 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
termui "github.com/gizak/termui/v3"
|
||
|
"github.com/gizak/termui/v3/widgets"
|
||
|
"io/ioutil"
|
||
|
"log"
|
||
|
// "math"
|
||
|
"net/http"
|
||
|
"strconv"
|
||
|
"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"
|
||
|
|
||
|
func (h *HnStories) populateStories(numstories int, done chan bool, widget *widgets.Gauge) {
|
||
|
var item string
|
||
|
var itemUrl string
|
||
|
var counter int = 1
|
||
|
for _, id := range h.Items[:numstories] {
|
||
|
item = strconv.Itoa(id)
|
||
|
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 item HnItem
|
||
|
|
||
|
json.Unmarshal(storydata, &item)
|
||
|
|
||
|
h.Stories = append(h.Stories, HnItem{
|
||
|
By: item.By,
|
||
|
Id: item.Id,
|
||
|
Added: item.Added,
|
||
|
Title: item.Title,
|
||
|
Type: item.Type,
|
||
|
Url: item.Url,
|
||
|
Kids: item.Kids,
|
||
|
})
|
||
|
counter++
|
||
|
fcounter := float64(counter)
|
||
|
fnumstories := float64(numstories)
|
||
|
percentage := (fcounter / fnumstories) * 100
|
||
|
widget.Percent = int(percentage)
|
||
|
termui.Render(widget)
|
||
|
}
|
||
|
widget.Percent = 100
|
||
|
done <- true
|
||
|
}
|
||
|
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, done chan []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++
|
||
|
}
|
||
|
done <- 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 numstories = 10
|
||
|
done1 := make(chan bool, 10)
|
||
|
done2 := make(chan bool, 10)
|
||
|
g0.Percent = 1
|
||
|
termui.Render(g0)
|
||
|
go a.httpHNFetchIds(hnurl, done1)
|
||
|
<-done1
|
||
|
go a.populateStories(numstories, done2, g0)
|
||
|
<-done2
|
||
|
termui.Clear()
|
||
|
l := widgets.NewList()
|
||
|
//l.Title = "[Y] hacker news"
|
||
|
termuiitems := make(chan []string, numstories)
|
||
|
go a.httpHNDisplayStories(numstories, termuiitems)
|
||
|
l.Border = false
|
||
|
l.SetRect(0, 1, x, y)
|
||
|
l.Rows = <-termuiitems
|
||
|
l.TextStyle = termui.NewStyle(termui.ColorYellow)
|
||
|
l.WrapText = true
|
||
|
x, y = termui.TerminalDimensions()
|
||
|
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", "<C-c>":
|
||
|
return
|
||
|
case "j", "<Down>":
|
||
|
l.ScrollDown()
|
||
|
case "k", "<Up>":
|
||
|
l.ScrollUp()
|
||
|
case "<C-d>":
|
||
|
l.ScrollHalfPageDown()
|
||
|
case "<C-u>":
|
||
|
l.ScrollHalfPageUp()
|
||
|
case "<C-f>":
|
||
|
l.ScrollPageDown()
|
||
|
case "<C-b>":
|
||
|
l.ScrollPageUp()
|
||
|
case "g":
|
||
|
if previousKey == "g" {
|
||
|
l.ScrollTop()
|
||
|
}
|
||
|
case "<Home>":
|
||
|
l.ScrollTop()
|
||
|
case "G", "<End>":
|
||
|
l.ScrollBottom()
|
||
|
case "<Resize>":
|
||
|
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)
|
||
|
}
|
||
|
}
|