Rename app. Also add Lobsters as tab#2.

This commit is contained in:
Kalle Carlbark 2019-08-17 21:24:40 +02:00
parent a92846a8eb
commit 4cb5097341
No known key found for this signature in database
GPG key ID: 3FC0C93C5A5A0670
2 changed files with 441 additions and 229 deletions

441
hmhm.go Normal file
View file

@ -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", "<C-c>":
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", "<Down>":
if tabpane.ActiveTabIndex == 0 {
hlist.ScrollDown()
}
if tabpane.ActiveTabIndex == 1 {
llist.ScrollDown()
}
case "k", "<Up>":
if tabpane.ActiveTabIndex == 0 {
hlist.ScrollUp()
}
if tabpane.ActiveTabIndex == 1 {
llist.ScrollUp()
}
case "<C-d>":
if tabpane.ActiveTabIndex == 0 {
hlist.ScrollHalfPageDown()
}
if tabpane.ActiveTabIndex == 1 {
llist.ScrollHalfPageDown()
}
case "<C-u>":
if tabpane.ActiveTabIndex == 0 {
hlist.ScrollHalfPageUp()
}
if tabpane.ActiveTabIndex == 1 {
llist.ScrollHalfPageUp()
}
case "<C-f>":
if tabpane.ActiveTabIndex == 0 {
hlist.ScrollPageDown()
}
if tabpane.ActiveTabIndex == 1 {
llist.ScrollPageDown()
}
case "<C-b>":
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 "<Home>":
if tabpane.ActiveTabIndex == 0 {
hlist.ScrollTop()
}
if tabpane.ActiveTabIndex == 1 {
llist.ScrollTop()
}
case "G", "<End>":
if tabpane.ActiveTabIndex == 0 {
hlist.ScrollBottom()
}
if tabpane.ActiveTabIndex == 1 {
llist.ScrollBottom()
}
case "<Enter>":
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 "<Resize>":
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)
}
}

View file

@ -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", "<C-c>":
return
case "r":
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 "<Enter>":
url := a.Stories[l.SelectedRow].Url
err := exec.Command("open", "-a", "Safari", url).Start()
if err != nil {
log.Fatal(err)
}
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)
}
}
}