all repos — flounder @ main

A small site builder for the Gemini protocol

gemfeed.go (view raw)

  1// Parses Gemfeed according to the companion spec:
  2// gemini://gemini.circumlunar.space/docs/companion/subscription.gmi
  3package main
  4
  5import (
  6	"bufio"
  7	"bytes"
  8	"github.com/gorilla/feeds"
  9	"io/ioutil"
 10	"net/url"
 11	"os"
 12	"path"
 13	"path/filepath"
 14	"sort"
 15	"strings"
 16	"time"
 17
 18	"git.sr.ht/~adnano/go-gemini"
 19)
 20
 21type Gemfeed struct {
 22	Title   string
 23	Creator string
 24	Url     *url.URL
 25	Entries []FeedEntry
 26}
 27
 28func (gf *Gemfeed) toAtomFeed() string {
 29	feed := feeds.Feed{
 30		Title:  gf.Title,
 31		Author: &feeds.Author{Name: gf.Creator},
 32		Link:   &feeds.Link{Href: gf.Url.String()},
 33	}
 34	feed.Items = []*feeds.Item{}
 35	for _, fe := range gf.Entries {
 36		feed.Items = append(feed.Items, &feeds.Item{
 37			Title:   fe.Title,
 38			Link:    &feeds.Link{Href: fe.Url.String()}, // Rel=alternate?
 39			Created: fe.Date,                            // Updated not created?
 40			Content: fe.Content,
 41		})
 42	}
 43	res, _ := feed.ToAtom()
 44	return res
 45}
 46
 47type FeedEntry struct {
 48	Title      string
 49	Url        *url.URL
 50	Date       time.Time
 51	DateString string
 52	Feed       *Gemfeed
 53	File       string // TODO refactor
 54	Content    string
 55}
 56
 57func urlFromPath(fullPath string) url.URL {
 58	creator := getCreator(fullPath)
 59	baseUrl := url.URL{}
 60	baseUrl.Host = creator + "." + c.Host
 61	baseUrl.Path = getLocalPath(fullPath)
 62	return baseUrl
 63}
 64
 65// Non-standard extension
 66// Requires yyyy-mm-dd formatted files
 67func generateFeedFromUser(user string) *Gemfeed {
 68	gemlogFolderPath := path.Join(c.FilesDirectory, user, GemlogFolder)
 69	// NOTE: assumes sanitized input
 70	u := urlFromPath(gemlogFolderPath)
 71	feed := Gemfeed{
 72		Title:   strings.Title(user) + "'s Gemlog",
 73		Creator: user,
 74		Url:     &u,
 75	}
 76	err := filepath.Walk(gemlogFolderPath, func(thepath string, info os.FileInfo, err error) error {
 77		base := path.Base(thepath)
 78		if len(base) >= 10 {
 79			entry := FeedEntry{}
 80			date, err := time.Parse("2006-01-02", base[:10])
 81			if err != nil {
 82				return nil
 83			}
 84			entry.Date = date
 85			entry.DateString = base[:10]
 86			entry.Feed = &feed
 87			content, err := ioutil.ReadFile(thepath)
 88			if err != nil {
 89				return nil
 90			}
 91			parse, _ := gemini.ParseText(bytes.NewReader(content))
 92			htmlDoc := textToHTML(nil, parse)
 93			if htmlDoc.Title != "" {
 94				// look for a title in headings
 95				entry.Title = htmlDoc.Title
 96			} else {
 97				// look for a title in lines
 98				f, err := os.Open(thepath)
 99				if err != nil {
100					return nil
101				}
102				defer f.Close()
103				scanner := bufio.NewScanner(f)
104				for scanner.Scan() {
105					// skip blank lines
106					if scanner.Text() == "" {
107						continue
108					}
109					line := scanner.Text()
110						var title string
111						if len(line) > 50 {
112							title = line[:50]
113						} else {
114							title = line
115						}
116						entry.Title = "[" + title + "...]"
117					break
118				}
119			}
120			entry.Content = htmlDoc.Content
121			entry.File = getLocalPath(thepath)
122			u := urlFromPath(thepath)
123			entry.Url = &u
124			feed.Entries = append(feed.Entries, entry)
125		}
126		return nil
127	})
128	if err != nil {
129		return nil
130	}
131	// Reverse chronological sort
132	sort.Slice(feed.Entries, func(i, j int) bool {
133		return feed.Entries[i].Date.After(feed.Entries[j].Date)
134	})
135	return &feed
136}