package spc import ( "encoding/xml" "fmt" "strings" "time" ) // RSSFeed is a minimal view of the optional SPC RSS feed. type RSSFeed struct { Title string Link string Description string LastBuildDate *time.Time Items []RSSItem } // RSSItem is a minimal view of one optional SPC RSS item. type RSSItem struct { Title string Link string Description string PubDate string GUID string } // ParseRSSFeed decodes supplemental SPC RSS metadata. func ParseRSSFeed(raw string) (RSSFeed, error) { var doc struct { Channel struct { Title string `xml:"title"` Link string `xml:"link"` Description string `xml:"description"` LastBuildDate string `xml:"lastBuildDate"` Items []struct { Title string `xml:"title"` Link string `xml:"link"` Description string `xml:"description"` PubDate string `xml:"pubDate"` GUID string `xml:"guid"` } `xml:"item"` } `xml:"channel"` } if err := xml.Unmarshal([]byte(raw), &doc); err != nil { return RSSFeed{}, fmt.Errorf("decode rss: %w", err) } feed := RSSFeed{ Title: strings.TrimSpace(doc.Channel.Title), Link: strings.TrimSpace(doc.Channel.Link), Description: strings.TrimSpace(doc.Channel.Description), LastBuildDate: parseRSSDate(doc.Channel.LastBuildDate), Items: make([]RSSItem, 0, len(doc.Channel.Items)), } for _, item := range doc.Channel.Items { feed.Items = append(feed.Items, RSSItem{ Title: strings.TrimSpace(item.Title), Link: strings.TrimSpace(item.Link), Description: strings.TrimSpace(item.Description), PubDate: strings.TrimSpace(item.PubDate), GUID: strings.TrimSpace(item.GUID), }) } return feed, nil } func parseRSSDate(value string) *time.Time { value = strings.TrimSpace(value) if value == "" { return nil } for _, layout := range []string{time.RFC1123Z, time.RFC1123} { t, err := time.Parse(layout, value) if err == nil { tt := t.UTC() return &tt } } return nil }