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  }