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}