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