arnaucube
arnaucube/blogoPublic Self-hosted repository
blogo/rss.go
rss.goRaw
  1package main
  2
  3import (
  4	"bytes"
  5	"encoding/xml"
  6	"fmt"
  7	"html"
  8	"log"
  9	"strings"
 10	"time"
 11)
 12
 13const rssFilename = "rss.xml"
 14
 15type rssDocument struct {
 16	XMLName xml.Name   `xml:"rss"`
 17	Version string     `xml:"version,attr"`
 18	Channel rssChannel `xml:"channel"`
 19}
 20
 21type rssChannel struct {
 22	Title       string    `xml:"title"`
 23	Link        string    `xml:"link"`
 24	Description string    `xml:"description"`
 25	Items       []rssItem `xml:"item"`
 26}
 27
 28type rssItem struct {
 29	Title       string  `xml:"title"`
 30	Link        string  `xml:"link"`
 31	Description string  `xml:"description,omitempty"`
 32	PubDate     string  `xml:"pubDate,omitempty"`
 33	GUID        rssGUID `xml:"guid"`
 34}
 35
 36type rssGUID struct {
 37	IsPermaLink bool   `xml:"isPermaLink,attr"`
 38	Value       string `xml:",chardata"`
 39}
 40
 41func generateRSS() {
 42	items := make([]rssItem, 0, len(config.Posts))
 43	for _, post := range config.Posts {
 44		source := post.Md
 45		if post.OutsideArticle != "" || source == "" {
 46			source = post.Thumb
 47		}
 48
 49		title := markdownTitle(readFile(directory + "/" + config.PostsDir + source))
 50		link := post.OutsideArticle
 51		if link == "" {
 52			filename := strings.Split(post.Md, ".")[0] + ".html"
 53			link = joinURL(config.AbsoluteUrl, filename)
 54		}
 55
 56		pubDate, err := rssDate(post.Date)
 57		if err != nil {
 58			log.Printf("invalid RSS date for %s: %v", source, err)
 59		}
 60
 61		items = append(items, rssItem{
 62			Title:       title,
 63			Link:        link,
 64			Description: post.MetaDescr,
 65			PubDate:     pubDate,
 66			GUID:        rssGUID{IsPermaLink: true, Value: link},
 67		})
 68	}
 69
 70	document := rssDocument{
 71		Version: "2.0",
 72		Channel: rssChannel{
 73			Title:       config.Title,
 74			Link:        joinURL(config.AbsoluteUrl),
 75			Description: config.MetaDescr,
 76			Items:       items,
 77		},
 78	}
 79
 80	var output bytes.Buffer
 81	output.WriteString(xml.Header)
 82	encoder := xml.NewEncoder(&output)
 83	encoder.Indent("", "  ")
 84	check(encoder.Encode(document))
 85	writeFile(config.OutputDir+"/"+rssFilename, output.String())
 86}
 87
 88func addRSSDiscovery(document string) string {
 89	feedURL := joinURL(config.AbsoluteUrl, rssFilename)
 90	tag := fmt.Sprintf(`<link rel="alternate" type="application/rss+xml" title="%s RSS" href="%s">`,
 91		html.EscapeString(config.Title), html.EscapeString(feedURL))
 92	if strings.Contains(document, tag) {
 93		return document
 94	}
 95
 96	headEnd := strings.Index(strings.ToLower(document), "</head>")
 97	if headEnd == -1 {
 98		return document
 99	}
100	return document[:headEnd] + tag + "\n" + document[headEnd:]
101}
102
103func markdownTitle(markdown string) string {
104	firstLine := strings.SplitN(markdown, "\n", 2)[0]
105	return strings.TrimSpace(strings.TrimLeft(firstLine, "#"))
106}
107
108func joinURL(parts ...string) string {
109	if len(parts) == 0 {
110		return ""
111	}
112
113	joined := strings.TrimRight(parts[0], "/")
114	for _, part := range parts[1:] {
115		part = strings.Trim(part, "/")
116		if part == "" {
117			continue
118		}
119		if joined == "" {
120			joined = "/" + part
121		} else {
122			joined += "/" + part
123		}
124	}
125	return joined
126}
127
128func rssDate(value string) (string, error) {
129	if value == "" {
130		return "", nil
131	}
132
133	formats := []string{time.RFC3339, "2006-01-02"}
134	for _, format := range formats {
135		parsed, err := time.Parse(format, value)
136		if err == nil {
137			return parsed.Format(time.RFC1123Z), nil
138		}
139	}
140	return "", fmt.Errorf("%q must use RFC 3339 or YYYY-MM-DD format", value)
141}