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