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}