git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/feeds/rss.go (about) 1 package feeds 2 3 // rss support 4 // validation done according to spec here: 5 // http://cyber.law.harvard.edu/rss/rss.html 6 7 import ( 8 "encoding/xml" 9 "fmt" 10 "time" 11 ) 12 13 // private wrapper around the RssFeed which gives us the <rss>..</rss> xml 14 type RssFeedXml struct { 15 XMLName xml.Name `xml:"rss"` 16 Version string `xml:"version,attr"` 17 ContentNamespace string `xml:"xmlns:content,attr"` 18 Channel *RssFeed 19 } 20 21 type RssContent struct { 22 XMLName xml.Name `xml:"content:encoded"` 23 Content string `xml:",cdata"` 24 } 25 26 type RssImage struct { 27 XMLName xml.Name `xml:"image"` 28 Url string `xml:"url"` 29 Title string `xml:"title"` 30 Link string `xml:"link"` 31 Width int `xml:"width,omitempty"` 32 Height int `xml:"height,omitempty"` 33 } 34 35 type RssTextInput struct { 36 XMLName xml.Name `xml:"textInput"` 37 Title string `xml:"title"` 38 Description string `xml:"description"` 39 Name string `xml:"name"` 40 Link string `xml:"link"` 41 } 42 43 type RssFeed struct { 44 XMLName xml.Name `xml:"channel"` 45 Title string `xml:"title"` // required 46 Link string `xml:"link"` // required 47 Description string `xml:"description"` // required 48 Language string `xml:"language,omitempty"` 49 Copyright string `xml:"copyright,omitempty"` 50 ManagingEditor string `xml:"managingEditor,omitempty"` // Author used 51 WebMaster string `xml:"webMaster,omitempty"` 52 PubDate string `xml:"pubDate,omitempty"` // created or updated 53 LastBuildDate string `xml:"lastBuildDate,omitempty"` // updated used 54 Category string `xml:"category,omitempty"` 55 Generator string `xml:"generator,omitempty"` 56 Docs string `xml:"docs,omitempty"` 57 Cloud string `xml:"cloud,omitempty"` 58 Ttl int `xml:"ttl,omitempty"` 59 Rating string `xml:"rating,omitempty"` 60 SkipHours string `xml:"skipHours,omitempty"` 61 SkipDays string `xml:"skipDays,omitempty"` 62 Image *RssImage 63 TextInput *RssTextInput 64 Items []*RssItem `xml:"item"` 65 } 66 67 type RssItem struct { 68 XMLName xml.Name `xml:"item"` 69 Title string `xml:"title"` // required 70 Link string `xml:"link"` // required 71 Description string `xml:"description"` // required 72 Content *RssContent 73 Author string `xml:"author,omitempty"` 74 Category string `xml:"category,omitempty"` 75 Comments string `xml:"comments,omitempty"` 76 Enclosure *RssEnclosure 77 Guid string `xml:"guid,omitempty"` // Id used 78 PubDate string `xml:"pubDate,omitempty"` // created or updated 79 Source string `xml:"source,omitempty"` 80 } 81 82 type RssEnclosure struct { 83 //RSS 2.0 <enclosure url="http://example.com/file.mp3" length="123456789" type="audio/mpeg" /> 84 XMLName xml.Name `xml:"enclosure"` 85 Url string `xml:"url,attr"` 86 Length string `xml:"length,attr"` 87 Type string `xml:"type,attr"` 88 } 89 90 type Rss struct { 91 *Feed 92 } 93 94 // create a new RssItem with a generic Item struct's data 95 func newRssItem(i *Item) *RssItem { 96 item := &RssItem{ 97 Title: i.Title, 98 Link: i.Link.Href, 99 Description: i.Description, 100 Guid: i.Id, 101 PubDate: anyTimeFormat(time.RFC1123Z, i.Created, i.Updated), 102 } 103 if len(i.Content) > 0 { 104 item.Content = &RssContent{Content: i.Content} 105 } 106 if i.Source != nil { 107 item.Source = i.Source.Href 108 } 109 110 // Define a closure 111 if i.Enclosure != nil && i.Enclosure.Type != "" && i.Enclosure.Length != "" { 112 item.Enclosure = &RssEnclosure{Url: i.Enclosure.Url, Type: i.Enclosure.Type, Length: i.Enclosure.Length} 113 } 114 115 if i.Author != nil { 116 item.Author = i.Author.Name 117 } 118 return item 119 } 120 121 // create a new RssFeed with a generic Feed struct's data 122 func (r *Rss) RssFeed() *RssFeed { 123 pub := anyTimeFormat(time.RFC1123Z, r.Created, r.Updated) 124 build := anyTimeFormat(time.RFC1123Z, r.Updated) 125 author := "" 126 if r.Author != nil { 127 author = r.Author.Email 128 if len(r.Author.Name) > 0 { 129 author = fmt.Sprintf("%s (%s)", r.Author.Email, r.Author.Name) 130 } 131 } 132 133 var image *RssImage 134 if r.Image != nil { 135 image = &RssImage{Url: r.Image.Url, Title: r.Image.Title, Link: r.Image.Link, Width: r.Image.Width, Height: r.Image.Height} 136 } 137 138 channel := &RssFeed{ 139 Title: r.Title, 140 Link: r.Link.Href, 141 Description: r.Description, 142 ManagingEditor: author, 143 PubDate: pub, 144 LastBuildDate: build, 145 Copyright: r.Copyright, 146 Image: image, 147 Language: r.Language, 148 } 149 for _, i := range r.Items { 150 channel.Items = append(channel.Items, newRssItem(i)) 151 } 152 return channel 153 } 154 155 // FeedXml returns an XML-Ready object for an Rss object 156 func (r *Rss) FeedXml() interface{} { 157 // only generate version 2.0 feeds for now 158 return r.RssFeed().FeedXml() 159 160 } 161 162 // FeedXml returns an XML-ready object for an RssFeed object 163 func (r *RssFeed) FeedXml() interface{} { 164 return &RssFeedXml{ 165 Version: "2.0", 166 Channel: r, 167 ContentNamespace: "http://purl.org/rss/1.0/modules/content/", 168 } 169 }