all repos — flounder @ 4ebc002355333177c8bc4d060a623a9f3e40f16f

A small site builder for the Gemini protocol

gemfeed.go (view raw)

 1// Parses Gemfeed according to the companion spec: gemini://gemini.circumlunar.space/docs/companion/subscription.gmi
 2package main
 3
 4import (
 5	"bufio"
 6	"fmt"
 7	"io"
 8	"os"
 9	"path/filepath"
10	"sort"
11	"strings"
12	"time"
13)
14
15type Gemfeed struct {
16	Title   string
17	Entries []*FeedEntry
18}
19
20type FeedEntry struct {
21	Title      string
22	Url        string
23	Date       time.Time
24	FeedTitle  string
25	DateString string
26}
27
28// TODO definitely cache this function -- it reads EVERY gemini file on flounder.
29func getAllGemfeedEntries() ([]*FeedEntry, error) {
30	maxUserItems := 25
31	maxItems := 100
32	var feedEntries []*FeedEntry
33	err := filepath.Walk(c.FilesDirectory, func(thepath string, info os.FileInfo, err error) error {
34		if isGemini(info.Name()) {
35			f, err := os.Open(thepath)
36			feed, err := ParseGemfeed(f, maxUserItems) // TODO make configurable
37			if err == nil {
38				feedEntries = append(feedEntries, feed.Entries...)
39			}
40		}
41		return nil
42	})
43	if err != nil {
44		return nil, err
45	} else {
46		sort.Slice(feedEntries, func(i, j int) bool {
47			return feedEntries[i].Date.After(feedEntries[j].Date)
48		})
49		if len(feedEntries) > maxItems {
50			return feedEntries[:maxItems], nil
51		}
52		return feedEntries, nil
53	}
54}
55
56// Parsed Gemfeed text Returns error if not a gemfeed
57// Doesn't sort output
58// Doesn't get posts dated in the future
59// if limit > -1 -- limit how many we are getting
60func ParseGemfeed(text io.Reader, limit int) (*Gemfeed, error) {
61	scanner := bufio.NewScanner(text)
62	gf := Gemfeed{}
63	for scanner.Scan() {
64		if limit > -1 && len(gf.Entries) >= limit {
65			break
66		}
67		line := scanner.Text()
68		if gf.Title == "" && strings.HasPrefix(line, "#") && !strings.HasPrefix(line, "##") {
69			gf.Title = strings.Trim(line[1:], " \t")
70		} else if strings.HasPrefix(line, "=>") {
71			link := strings.Trim(line[2:], " \t")
72			splits := strings.SplitN(link, " ", 2)
73			if len(splits) == 2 && len(splits[1]) >= 10 {
74				dateString := splits[1][:10]
75				date, err := time.Parse("2006-01-02", dateString)
76				if err == nil && time.Now().After(date) {
77					title := strings.Trim(splits[1][10:], " -\t")
78					fe := FeedEntry{title, splits[0], date, gf.Title, dateString}
79					gf.Entries = append(gf.Entries, &fe)
80				}
81			}
82		}
83	}
84	if len(gf.Entries) == 0 {
85		return nil, fmt.Errorf("No Gemfeed entries found")
86	}
87	return &gf, nil
88}