commit - 2a4edef3d088dbb3d4653ffa1edf6920955c7f79
commit + d4b74d7a68368284eca18e00ef626542ae8bb20c
blob - 1bf408994b782ce9c379ac83c8ec227c149918e4
blob + 299037dd51bf3491f5aa63d17622b10169a39aa1
--- cmd/Lemmy/Lemmy.go
+++ cmd/Lemmy/Lemmy.go
import (
"bytes"
- "errors"
"flag"
"fmt"
- "io/fs"
"log"
"os"
"path"
+ "strconv"
"strings"
+ "time"
"9fans.net/go/acme"
"olowe.co/lemmy"
- lemmyfs "olowe.co/lemmy/fs"
)
type awin struct {
}
text = strings.TrimSpace(text)
- f, err := fsys.Open(text)
- if err == nil {
- f.Close()
- return open(text)
- } else if errors.Is(err, fs.ErrNotExist) {
- // maybe we're looking for a post in a community?
- if win.community() != "" {
- name := path.Join(win.community(), text)
- f, err = fsys.Open(name)
- if err == nil {
- f.Close()
- return open(name)
- }
- }
+ text = strings.TrimSuffix(text, "/")
+ postID, err := strconv.Atoi(text)
+ if err != nil {
+ return openCommunity(text)
}
- if !errors.Is(err, fs.ErrNotExist) && !errors.Is(err, fs.ErrInvalid) {
- win.Err(err.Error())
- }
+
+ community := path.Base(win.name())
+ return openPost(postID, community)
return false
}
func (win *awin) Execute(cmd string) bool {
switch cmd {
- case "Reply":
- return open("reply")
- case "Post":
- body, err := win.ReadAll("body")
- if err != nil {
- win.Err(err.Error())
- return false
- }
- comment, err := parseReply(bytes.NewReader(body))
- if err != nil {
- win.Err("parse comment reply: " + err.Error())
- return false
- }
- client := fsys.(*lemmyfs.FS).Client
- if err := client.Reply(comment.PostID, 0, comment.Content); err != nil {
- emsg := fmt.Sprintf("reply to %d: %v", comment.PostID, err)
- win.Err(emsg)
- return false
- }
- win.Ctl("clean")
- return true
case "Del":
default:
log.Println("unsupported execute", cmd)
return false
}
-func (w *awin) community() string {
- elems := strings.Split(w.name(), "/")
- switch len(elems) {
- case 2:
- // /lemmy/community
- return elems[1]
- case 3:
- // /lemmy/community/post
- return elems[2]
- }
- return ""
-}
-
func (w *awin) name() string {
buf, err := w.ReadAll("tag")
if err != nil {
return path.Clean(name)
}
-var fsys fs.FS
+var client *lemmy.Client
-func loadPostList(dir string) ([]byte, error) {
+func loadPostList(community string) ([]byte, error) {
buf := &bytes.Buffer{}
- dirents, err := fs.ReadDir(fsys, dir)
+ posts, err := client.Posts(community, lemmy.ListAll)
if err != nil {
return buf.Bytes(), err
}
- for _, d := range dirents {
- title, err := fs.ReadFile(fsys, path.Join(dir, d.Name(), "title"))
- if err != nil {
- return nil, err
- }
- creator, err := fs.ReadFile(fsys, path.Join(dir, d.Name(), "creator"))
- if err != nil {
- return buf.Bytes(), err
- }
+ for _, p := range posts {
// 1234/ User
// Hello world!
// 5678/ Pengguna
// Halo Dunia!
- fmt.Fprintf(buf, "%s/\t%s\n\t%s\n", d.Name(), string(creator), string(title))
+ fmt.Fprintf(buf, "%d/\t%s\n\t%s\n", p.ID, p.Creator, p.Title)
}
return buf.Bytes(), err
}
-func loadPost(pathname string) ([]byte, error) {
+func loadPost(post lemmy.Post) ([]byte, error) {
buf := &bytes.Buffer{}
- for _, fname := range []string{"title", "creator", "url"} {
- b, err := fs.ReadFile(fsys, path.Join(pathname, fname))
- if err != nil {
- return buf.Bytes(), err
- }
- key := fmt.Sprintf("%s:", strings.Title(fname))
- fmt.Fprintln(buf, key, string(b))
- }
- /*
- stat, err := fs.Stat(fsys, pathname)
- if err != nil {
- return nil, err
- }
- fmt.Fprintln(buf, "Date:", stat.ModTime())
- */
- fmt.Fprintln(buf)
+ fmt.Fprintf(buf, "From: %s\n", post.Creator)
+ fmt.Fprintf(buf, "Date: %s\n", post.Published.Format(time.RFC822))
+ fmt.Fprintf(buf, "Subject: %s\n", post.Title)
- body, err := fs.ReadFile(fsys, path.Join(pathname, "body"))
- if err != nil {
- return buf.Bytes(), err
+ fmt.Fprintln(buf)
+ if post.URL != "" {
+ fmt.Fprintln(buf, post.URL)
+ fmt.Fprintln(buf)
}
- fmt.Fprintln(buf, string(body))
- fmt.Fprintln(buf, "")
- dirents, err := fs.ReadDir(fsys, pathname)
- if err != nil {
- return buf.Bytes(), err
+ if post.Body != "" {
+ fmt.Fprintln(buf, post.Body)
+ fmt.Fprintln(buf)
}
- for _, ent := range dirents {
- if !strings.ContainsAny(ent.Name(), "0123456789") {
- continue
- }
- comment, err := fs.ReadFile(fsys, path.Join(pathname, ent.Name()))
- if err != nil {
- return buf.Bytes(), err
- }
- fmt.Fprintln(buf, string(comment))
- }
return buf.Bytes(), nil
}
-func open(name string) bool {
- win, err := acme.New()
+func loadComments(id int) ([]byte, error) {
+ comments, err := client.Comments(id, lemmy.ListAll)
if err != nil {
- log.Fatal(err)
+ return nil, err
}
- win.Name(path.Join("/lemmy", name))
- if name == "." || name == "" {
- win.Name("/lemmy/")
- }
- win.Ctl("dirty")
- defer win.Ctl("clean")
-
- var body []byte
- elems := strings.Split(name, "/")
- switch len(elems) {
- case 1:
- if name == "." {
- body, err = loadCommunityList()
- break
- } else if path.Base(name) == "reply" {
- win.Write("tag", []byte("Post"))
- body = loadNewReply("")
- break
+ buf := &bytes.Buffer{}
+ for _, c := range comments {
+ refs := lemmy.ParseCommentPath(c.Path)
+ // do we have a root comment?
+ // A root comment only referenences itself and "0"
+ if len(refs) == 2 {
+ fprintComment(buf, "", c)
+ printThread(buf, "\t", c.ID, comments)
}
- body, err = loadPostList(name)
- case 2:
- win.Write("tag", []byte("Reply"))
- body, err = loadPost(name)
}
- if errors.Is(err, fs.ErrNotExist) {
- return false
- } else if err != nil {
- log.Print(err)
- return false
- }
-
- awin := &awin{win}
- awin.Write("body", body)
- go awin.EventLoop(awin)
- return true
+ return buf.Bytes(), nil
}
const Usage string = "usage: Lemmy [host]"
} else if len(flag.Args()) == 1 {
addr = flag.Arg(0)
}
- client := &lemmy.Client{
+ client = &lemmy.Client{
Address: addr,
Debug: *debug,
}
}
}
- fsys = &lemmyfs.FS{
- Client: client,
- }
-
- open(".")
+ openCommunityList()
acme.AutoExit(true)
select {}
}
blob - ca5c746091babfdbb6fa7f483f1cef7244233ca2
blob + 9b645de3870778f579e1f3eab608be62c1b56010
--- cmd/Lemmy/comment.go
+++ cmd/Lemmy/comment.go
}
return &comment, nil
}
+
+func printThread(w io.Writer, prefix string, parent int, comments []lemmy.Comment) {
+ for _, child := range children(parent, comments) {
+ fprintComment(w, prefix, child)
+ if len(children(child.ID, comments)) > 0 {
+ printThread(w, prefix+"\t", child.ID, comments)
+ }
+ }
+}
+
+func fprintComment(w io.Writer, prefix string, c lemmy.Comment) {
+ fmt.Fprintln(w, prefix, "From:", c.Creator)
+ fmt.Fprintln(w, prefix, "Archived-At:", c.ActivityURL)
+ fmt.Fprintln(w, prefix, c.Content)
+ println()
+}
+
+func children(parent int, pool []lemmy.Comment) []lemmy.Comment {
+ var kids []lemmy.Comment
+ for _, c := range pool {
+ refs := lemmy.ParseCommentPath(c.Path)
+ pnt := refs[len(refs)-2]
+ if pnt == parent {
+ kids = append(kids, c)
+ }
+ }
+ return kids
+}
blob - e321a7039b95ea3a1ff5ee6d40ebdbc7dd1982e9 (mode 644)
blob + /dev/null
--- cmd/Lemmy/fmt.go
+++ /dev/null
-package main
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "io/fs"
- "path"
- "strings"
-)
-
-func printDirEntries(w io.Writer, dirs []fs.DirEntry) {
- for _, entry := range dirs {
- name := entry.Name()
- if entry.IsDir() {
- name = name + "/"
- }
- fmt.Fprintln(w, name)
- }
-}
-
-func listDir(name string) (string, error) {
- entries, err := fs.ReadDir(fsys, name)
- if err != nil {
- return "", err
- }
- var builder strings.Builder
- for _, entry := range entries {
- filename := path.Join(name, entry.Name(), "title")
- title, err := fs.ReadFile(fsys, filename)
- if err != nil && !errors.Is(err, io.EOF) {
- return "", err
- }
- line := fmt.Sprintf("%s/\t%s\n", entry.Name(), string(title))
- builder.WriteString(line)
- }
- return builder.String(), nil
-}
-
-func loadCommunityList() ([]byte, error) {
- entries, err := fs.ReadDir(fsys, ".")
- if err != nil {
- return nil, err
- }
- buf := &bytes.Buffer{}
- for _, dirent := range entries {
- fmt.Fprintf(buf, "%s/\n", dirent.Name())
- }
- return buf.Bytes(), nil
-}
blob - /dev/null
blob + 87fab1bf1a0a072c7a0c0bf4063e7f3bfb96f130 (mode 644)
--- /dev/null
+++ cmd/Lemmy/open.go
+package main
+
+import (
+ "errors"
+ "log"
+ "path"
+ "strconv"
+
+ "9fans.net/go/acme"
+ "olowe.co/lemmy"
+)
+
+func openCommunityList() bool {
+ win, err := acme.New()
+ if err != nil {
+ log.Fatal(err)
+ }
+ win.Name("/lemmy/")
+ win.Ctl("dirty")
+ defer win.Ctl("clean")
+
+ communities, err := client.Communities(lemmy.ListAll)
+ if err != nil {
+ log.Print(err)
+ return false
+ }
+ for _, c := range communities {
+ win.Fprintf("body", "%s/\n", c.Name())
+ }
+ awin := &awin{win}
+ go awin.EventLoop(awin)
+ return true
+}
+
+func openCommunity(name string) bool {
+ _, _, err := client.LookupCommunity(name)
+ if errors.Is(err, lemmy.ErrNotFound) {
+ return false
+ } else if err != nil {
+ log.Print(err)
+ return false
+ }
+
+ win, err := acme.New()
+ if err != nil {
+ log.Fatal(err)
+ }
+ win.Ctl("dirty")
+ defer win.Ctl("clean")
+
+ awin := &awin{win}
+ awin.Name(path.Join("/lemmy", name) + "/")
+
+ body, err := loadPostList(name)
+ if err != nil {
+ win.Err(err.Error())
+ return false
+ }
+ awin.Write("body", body)
+ win.Addr("#0")
+ win.Ctl("dot=addr")
+ win.Ctl("show")
+ go awin.EventLoop(awin)
+ return true
+}
+
+func openPost(id int, community string) bool {
+ post, err := client.LookupPost(id)
+ if err != nil {
+ log.Print(err)
+ return false
+ }
+
+ win, err := acme.New()
+ if err != nil {
+ log.Fatal(err)
+ }
+ awin := &awin{win}
+ awin.Name(path.Join("/lemmy", community, strconv.Itoa(id)))
+ win.Ctl("dirty")
+ defer win.Ctl("clean")
+
+ body, err := loadPost(post)
+ if err != nil {
+ awin.Err(err.Error())
+ return false
+ }
+ awin.Write("body", body)
+
+ body, err = loadComments(post.ID)
+ if err != nil {
+ awin.Err(err.Error())
+ return false
+ }
+ awin.Write("body", body)
+
+ win.Addr("#0")
+ win.Ctl("dot=addr")
+ win.Ctl("show")
+ go awin.EventLoop(awin)
+ return true
+}