Skip to content

Commit

Permalink
add discourse support
Browse files Browse the repository at this point in the history
Squashed commit of the following:

commit 3382b02
Author: azimut <azimut.github@protonmail.com>
Date:   Fri Apr 28 12:41:42 2023 -0300

    discourse: dramatic author names

commit 5ce96e2
Author: azimut <azimut.github@protonmail.com>
Date:   Fri Apr 28 12:25:11 2023 -0300

    discourse: add comment tree graph

commit c04461a
Author: azimut <azimut.github@protonmail.com>
Date:   Fri Apr 28 10:30:34 2023 -0300

    readme: update reddit desc

commit ff92224
Author: azimut <azimut.github@protonmail.com>
Date:   Fri Apr 28 10:29:40 2023 -0300

    discourse: add working tui/cli, missing comments tree graph
  • Loading branch information
azimut committed Apr 28, 2023
1 parent 3323475 commit 3bb015e
Show file tree
Hide file tree
Showing 17 changed files with 476 additions and 38 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/redditview
/fourchanview
/vichanview
/discourseview

# Created by https://www.toptal.com/developers/gitignore/api/go
# Edit at https://www.toptal.com/developers/gitignore?templates=go
Expand Down
7 changes: 7 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,10 @@ builds:
goos: [ linux ]
goarch: [ amd64 ]
ldflags: [ "-s -w" ]

- id: discourseview
binary: discourseview
main: ./cmd/discourseview
goos: [ linux ]
goarch: [ amd64 ]
ldflags: [ "-s -w" ]
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
GO_FILES := $(shell find . -type f -name '*.go')
BINARIES := twitterview hackerview redditview fourchanview vichanview
BINARIES := twitterview hackerview redditview fourchanview vichanview discourseview
LDFLAGS := -ldflags="-s -w"

ifdef DEBUG
Expand Down
2 changes: 1 addition & 1 deletion README.org
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ tonyg(211) - 5 hours ago
>>> saurik - 33 minutes ago
#+end_src
** Reddit
- Uses their [[https://www.reddit.com/dev/api/][.json API]]
- Uses their [[https://www.reddit.com/dev/api/][.json API]], parsing the markdown comment in /.body/
#+begin_src
~ » twitterview https://www.reddit.com/r/rust/comments/120mjef/how_to_learn_rust/

Expand Down
80 changes: 80 additions & 0 deletions cmd/discourseview/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package main

import (
"errors"
"flag"
"fmt"
"io"
"log"
"os"
"time"

"github.com/azimut/cli-view/internal/discourse"
"github.com/azimut/cli-view/internal/tui"
"github.com/fatih/color"
)

type options struct {
leftPadding uint
timeout time.Duration
useColors bool
showAuthor bool
showDate bool
showId bool
userAgent string
useTUI bool
width uint
}

var opts options

func init() {
flag.BoolVar(&opts.useColors, "C", true, "use colors")
flag.BoolVar(&opts.showDate, "d", false, "print date on comments")
flag.BoolVar(&opts.showAuthor, "a", true, "print author on comments")
flag.BoolVar(&opts.useTUI, "x", false, "use TUI")
flag.DurationVar(&opts.timeout, "t", time.Second*5, "timeout in seconds")
flag.StringVar(&opts.userAgent, "A", "DiscourseView/1.0", "user agent to send")
flag.UintVar(&opts.width, "w", 100, "terminal width")
flag.UintVar(&opts.leftPadding, "l", 3, "left padding on comments")
}

func usage() {
fmt.Printf("Usage: %s [OPTIONS] URL ...\n", os.Args[0])
flag.PrintDefaults()
}

func run(args []string, stdout io.Writer) error {
flag.Parse()
flag.Usage = usage
color.NoColor = !opts.useColors
if flag.NArg() != 1 {
flag.Usage()
return errors.New("missing URL argument")
}

url := flag.Args()[0]
thread, err := discourse.Fetch(url, opts.userAgent, opts.timeout)
if err != nil {
return errors.New("could not fetch url")
}
thread.Width = int(opts.width)
thread.LeftPadding = int(opts.leftPadding)
thread.ShowAuthor = opts.showAuthor
thread.ShowDate = opts.showDate

if opts.useTUI {
tui.RenderLoop(discourse.NewProgram(*thread))
} else {
fmt.Println(thread)
}

return nil
}

func main() {
err := run(os.Args, os.Stdout)
if err != nil {
log.Fatal(err)
}
}
26 changes: 26 additions & 0 deletions internal/discourse/fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package discourse

import (
"time"

"github.com/azimut/cli-view/internal/fetch"
)

func Fetch(rawUrl, userAgent string, timeout time.Duration) (*Thread, error) {
url, err := effectiveUrl(rawUrl)
if err != nil {
return nil, err
}

rawJson, err := fetch.Fetch(url, userAgent, timeout)
if err != nil {
return nil, err
}

thread, err := toThread(rawJson, rawUrl)
if err != nil {
return nil, err
}

return thread, nil
}
24 changes: 24 additions & 0 deletions internal/discourse/fetch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package discourse

import (
"fmt"
"testing"
"time"

"github.com/dustin/go-humanize"
)

func TestFetch(t *testing.T) {
testUrl := "https://users.rust-lang.org/t/forum-code-formatting-and-syntax-highlighting/42214"
thread, err := Fetch(testUrl, "Mozilla", time.Second*5)
if err != nil {
t.Fail()
}
got := thread.id
expected := 42214
if got != expected {
fmt.Println(thread)
fmt.Println(humanize.Time(thread.op.createdAt))
t.Errorf("got %d expected %d", got, expected)
}
}
60 changes: 60 additions & 0 deletions internal/discourse/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package discourse

import (
"fmt"
"strings"

"github.com/azimut/cli-view/internal/format"
"github.com/charmbracelet/lipgloss"
"github.com/dustin/go-humanize"
)

var authorStyle = lipgloss.NewStyle().
Bold(true).
Background(lipgloss.Color("8")).
Foreground(lipgloss.Color("0"))

func (t Thread) String() (ret string) {
ret += fmt.Sprint(t.op)
for _, comment := range t.comments {
ret += fmt.Sprint(comment)
ret += "\n"
}
return
}

func (o Op) String() (ret string) {
ret += fmt.Sprintf("title: %s\n", o.title)
ret += fmt.Sprintf(" self: %s\n", o.thread.url)
ret += fmt.Sprintf(
"\n%s\n\n",
format.FormatHtml2Text(o.message, o.thread.Width, o.thread.LeftPadding),
)
ret += fmt.Sprintf("%s - %s \n\n\n", authorStyle.Render(o.author), humanize.Time(o.createdAt))
return
}

func (c Comment) String() (ret string) {
ret += fmt.Sprintf(
"%s\n",
format.FormatHtml2Text(c.message, c.thread.Width, c.thread.LeftPadding*c.depth+1),
)
ret += strings.Repeat(" ", c.depth*c.thread.LeftPadding)
ret += ">>"
if c.thread.ShowAuthor {
ret += " "
if c.author == c.thread.op.author {
ret += authorStyle.Render(c.author)
} else {
ret += c.author
}
}
if c.thread.ShowDate {
ret += " " + humanize.Time(c.createdAt)
}
ret += "\n\n"
for _, reply := range c.replies {
ret += fmt.Sprint(reply)
}
return
}
60 changes: 60 additions & 0 deletions internal/discourse/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package discourse

import (
"errors"

"github.com/tidwall/gjson"
)

func toThread(rawJson, rawUrl string) (*Thread, error) {
if !gjson.Valid(rawJson) {
return nil, errors.New("invalid json response")
}
parsedJson := gjson.Parse(rawJson)

var thread Thread
thread.id = int(parsedJson.Get("id").Int())
thread.url = rawUrl

op := toOp(parsedJson)
op.thread = &thread
thread.op = op

rawComments := parsedJson.Get("post_stream.posts").Array()[1:]
for _, rawComment := range rawComments {
if isAnAction(rawComment) {
continue
}
comment := toComment(rawComment)
comment.thread = &thread
thread.insertComment(comment)
}

return &thread, nil
}

func isAnAction(result gjson.Result) bool {
return result.Get("cooked").String() == "" && result.Get("action_code").String() != ""
}

func toOp(resultJson gjson.Result) Op {
resultOp := resultJson.Get("post_stream.posts.0")
return Op{
id: int(resultOp.Get("post_number").Int()),
author: resultOp.Get("username").String(),
message: resultOp.Get("cooked").String(),
createdAt: resultJson.Get("created_at").Time(),
title: resultJson.Get("title").String(),
}
}

func toComment(resultComment gjson.Result) Comment {
// TODO: depth, replies
return Comment{
author: resultComment.Get("username").String(),
createdAt: resultComment.Get("created_at").Time(),
id: int(resultComment.Get("post_number").Int()),
message: resultComment.Get("cooked").String(),
parentId: int(resultComment.Get("reply_to_post_number").Int()),
}
}
41 changes: 41 additions & 0 deletions internal/discourse/parse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package discourse

import (
"fmt"
"io/ioutil"
"testing"
)

func TestToThread(t *testing.T) {
testUrl := "https://users.rust-lang.org/t/forum-code-formatting-and-syntax-highlighting/42214/2"
testFile := "../../testdata/discourse-42214.json"
rawJson, err := ioutil.ReadFile(testFile)
if err != nil {
t.Error(err)
}

thread, err := toThread(string(rawJson), testUrl)
if err != nil {
t.Error(err)
}

fmt.Println(thread) // DEBUG

got := thread.id
expected := 42214
if expected != got {
t.Errorf("got %d expected %d", got, expected)
}

got = thread.op.id
expected = 1
if expected != got {
t.Errorf("got %d expected %d", got, expected)
}

got = len(thread.comments)
expected = 3
if expected != got {
t.Errorf("got %d expected %d", got, expected)
}
}
44 changes: 44 additions & 0 deletions internal/discourse/tui.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package discourse

import (
"fmt"

"github.com/azimut/cli-view/internal/tui"
tea "github.com/charmbracelet/bubbletea"
)

const rightPadding = 10

type Model struct {
render tui.Model
Thread
}

func NewProgram(thread Thread) *tea.Program {
return tea.NewProgram(Model{Thread: thread},
tea.WithAltScreen())
}

func (m Model) Init() tea.Cmd {
return nil
}

func (m Model) View() string {
return m.render.View()
}

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
// Initialize data to be used for links scrapping
if m.render.RawContent == "" {
m.Width = 300
m.render.RawContent = fmt.Sprint(m)
}
m.render, cmd = m.render.Update(msg)
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.Width = msg.Width - rightPadding
m.render.Viewport.SetContent(fmt.Sprint(m))
}
return m, cmd
}
Loading

0 comments on commit 3bb015e

Please sign in to comment.