all repos — flounder @ fbba2837f74ed229dd1a9fc32a3ddf12d1a397d3

A small site builder for the Gemini protocol

Add Gemfeed support
alex wennerberg alex@alexwennerberg.com
Tue, 15 Dec 2020 18:05:49 -0800
commit

fbba2837f74ed229dd1a9fc32a3ddf12d1a397d3

parent

92e3d828bff62e1549adf5a64f2ff55af993e466

6 files changed, 120 insertions(+), 2 deletions(-)

jump to
A gemfeed.go

@@ -0,0 +1,79 @@

+// Parses Gemfeed according to the companion spec: gemini://gemini.circumlunar.space/docs/companion/subscription.gmi +package main + +import ( + "bufio" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "strings" + "time" +) + +type Gemfeed struct { + Title string + Entries []*FeedEntry +} + +type FeedEntry struct { + Title string + Url string + Date time.Time + FeedTitle string + DateString string +} + +// TODO definitely cache this function -- it reads EVERY gemini file on flounder. +func getAllGemfeedEntries() ([]*FeedEntry, error) { + var feedEntries []*FeedEntry + err := filepath.Walk(c.FilesDirectory, func(thepath string, info os.FileInfo, err error) error { + if isGemini(info.Name()) { + f, err := os.Open(thepath) + feed, err := ParseGemfeed(f) + if err == nil { + feedEntries = append(feedEntries, feed.Entries...) + } + } + return nil + }) + if err != nil { + return nil, err + } else { + sort.Slice(feedEntries, func(i, j int) bool { + return feedEntries[i].Date.After(feedEntries[j].Date) + }) + return feedEntries, nil + } +} + +// Parsed Gemfeed text Returns error if not a gemfeed +// Doesn't sort output +// Doesn't get posts dated in the future +func ParseGemfeed(text io.Reader) (*Gemfeed, error) { + scanner := bufio.NewScanner(text) + gf := Gemfeed{} + for scanner.Scan() { + line := scanner.Text() + if gf.Title == "" && strings.HasPrefix(line, "#") && !strings.HasPrefix(line, "##") { + gf.Title = strings.Trim(line[1:], " \t") + } else if strings.HasPrefix(line, "=>") { + link := strings.Trim(line[2:], " \t") + splits := strings.SplitN(link, " ", 2) + if len(splits) == 2 && len(splits[1]) >= 10 { + dateString := splits[1][:10] + date, err := time.Parse("2006-01-02", dateString) + if err == nil && time.Now().After(date) { + title := strings.Trim(splits[1][10:], " -\t") + fe := FeedEntry{title, splits[0], date, gf.Title, dateString} + gf.Entries = append(gf.Entries, &fe) + } + } + } + } + if len(gf.Entries) == 0 { + return nil, fmt.Errorf("No Gemfeed entries found") + } + return &gf, nil +}
M http.gohttp.go

@@ -78,6 +78,24 @@ panic(err)

} } +func feedHandler(w http.ResponseWriter, r *http.Request) { + user := newGetAuthUser(r) + feedEntries, err := getAllGemfeedEntries() + if err != nil { + panic(err) + } + data := struct { + Host string + PageTitle string + FeedEntries []*FeedEntry + AuthUser AuthUser + }{c.Host, c.SiteTitle, feedEntries, user} + err = t.ExecuteTemplate(w, "feed.html", data) + if err != nil { + panic(err) + } +} + func editFileHandler(w http.ResponseWriter, r *http.Request) { user := newGetAuthUser(r) if !user.LoggedIn {

@@ -533,10 +551,9 @@ return

} // Dumb content negotiation - extension := path.Ext(fileName) _, raw := r.URL.Query()["raw"] acceptsGemini := strings.Contains(r.Header.Get("Accept"), "text/gemini") - if !raw && !acceptsGemini && (extension == ".gmi" || extension == ".gemini") { + if !raw && !acceptsGemini && isGemini(fileName) { file, _ := os.Open(fileName) htmlString := textToHTML(gmi.ParseText(file)) favicon := getFavicon(userName)

@@ -668,6 +685,7 @@ hostname := s[0]

port := c.HttpPort serveMux.HandleFunc(hostname+"/", rootHandler) + serveMux.HandleFunc(hostname+"/feed", feedHandler) serveMux.HandleFunc(hostname+"/my_site", mySiteHandler) serveMux.HandleFunc(hostname+"/me", myAccountHandler) serveMux.HandleFunc(hostname+"/my_site/flounder-archive.zip", archiveHandler)
A templates/feed.html

@@ -0,0 +1,14 @@

+{{$domain := .Host}} +{{template "header" .}} +<h1>🐟{{.PageTitle}} -- Feeds</h1> +{{template "nav.html" .}} +<br> +<p> +For more information on how to format your site to show up here, see <a href="https://admin.flounder.online/gemfeed.gmi">this documentation</a> +<h2>Feed:</h2> +{{ range .FeedEntries}} +<p> +<a href="{{.Url}}" class='person-link'>{{.DateString}} [{{.FeedTitle}}] — {{.Title}}</a> +</p> +{{end}} +{{template "footer" .}}
M templates/index.htmltemplates/index.html

@@ -5,6 +5,7 @@ {{template "nav.html" .}}

<br> <p> Welcome to flounder! For more information and site updates, check out the <a href="//admin.{{$domain}}">admin page</a></p> + <h2>All users:</h2> {{ range .Users}} <a href="//{{.}}.{{$domain}}" class='person-link'>{{.}}</a>
M templates/nav.htmltemplates/nav.html

@@ -1,5 +1,6 @@

<nav> <a href="/">home</a> + <a href="/feed">feed</a> {{ if .AuthUser.LoggedIn }} <a href="/my_site">my_site</a> <a href="/me">me</a>
M utils.goutils.go

@@ -11,6 +11,11 @@ "strings"

"time" ) +func isGemini(filename string) bool { + extension := path.Ext(filename) + return extension == ".gmi" || extension == ".gemini" +} + func timeago(t *time.Time) string { d := time.Since(*t) if d.Seconds() < 60 {