package nyaafeeds
// rss support
// validation done according to spec here:
// http://cyber.law.harvard.edu/rss/rss.html
import (
"encoding/xml"
"fmt"
"strconv"
"time"
"github.com/gorilla/feeds"
)
// private wrapper around the RssFeed which gives us the .. xml
type rssFeedXML struct {
XMLName xml.Name `xml:"rss"`
Version string `xml:"version,attr"`
Encoding string `xml:"encoding,attr"`
Channel *RssFeed `xml:"channel,omitempty"`
Caps *RssCaps `xml:"caps,omitempty"`
}
type RssImage struct {
XMLName xml.Name `xml:"image"`
URL string `xml:"url"`
Title string `xml:"title"`
Link string `xml:"link"`
Width int `xml:"width,omitempty"`
Height int `xml:"height,omitempty"`
}
type RssTextInput struct {
XMLName xml.Name `xml:"textInput"`
Title string `xml:"title"`
Description string `xml:"description"`
Name string `xml:"name"`
Link string `xml:"link"`
}
type RssFeed struct {
XMLName xml.Name `xml:"channel"`
Title string `xml:"title"` // required
Link string `xml:"link"` // required
Description string `xml:"description"` // required
Language string `xml:"language,omitempty"`
Copyright string `xml:"copyright,omitempty"`
ManagingEditor string `xml:"managingEditor,omitempty"` // Author used
WebMaster string `xml:"webMaster,omitempty"`
PubDate string `xml:"pubDate,omitempty"` // created or updated
LastBuildDate string `xml:"lastBuildDate,omitempty"` // updated used
Category string `xml:"category,omitempty"`
Generator string `xml:"generator,omitempty"`
Docs string `xml:"docs,omitempty"`
Cloud string `xml:"cloud,omitempty"`
TTL int `xml:"ttl,omitempty"`
Rating string `xml:"rating,omitempty"`
SkipHours string `xml:"skipHours,omitempty"`
SkipDays string `xml:"skipDays,omitempty"`
Image *RssImage
TextInput *RssTextInput
Items []*RssItem
}
type RssItem struct {
XMLName xml.Name `xml:"item"`
Title string `xml:"title"` // required
Link string `xml:"link"` // required
Description string `xml:"description"` // required
Author string `xml:"author,omitempty"`
Category *RssCategory `xml:"category,omitempty"`
Comments string `xml:"comments,omitempty"`
Enclosure *RssEnclosure
GUID string `xml:"guid,omitempty"` // Id used
PubDate string `xml:"pubDate,omitempty"` // created or updated
Source string `xml:"source,omitempty"`
Torrent *RssTorrent `xml:"torrent,omitempty"`
Torznab *RssTorznab `xml:"torznab,omitempty"`
}
type RssCaps struct {
XMLName xml.Name `xml:"caps"`
Server *RssServer `xml:"server,omitempty"`
Limits *RssLimits `xml:"limits,omitempty"`
Registration *RssRegistration `xml:"registration,omitempty"`
Searching *RssSearching `xml:"searching,omitempty"`
Categories *RssCategories `xml:"categories,omitempty"`
}
type RssServer struct {
XMLName xml.Name `xml:"server"`
Xmlns string `xml:"xmlns,attr"`
Version string `xml:"version,attr"`
Title string `xml:"title,attr"`
Strapline string `xml:"strapline,attr"`
Email string `xml:"email,attr"`
URL string `xml:"url,attr"`
Image string `xml:"image,attr"`
}
type RssLimits struct {
XMLName xml.Name `xml:"limits"`
Max string `xml:"limits,attr"`
Default string `xml:"default,attr"`
}
type RssRegistration struct {
XMLName xml.Name `xml:"registration"`
Available string `xml:"available,attr"`
Open string `xml:"open,attr"`
}
type RssSearching struct {
XMLName xml.Name `xml:"searching"`
Search *RssSearch `xml:"search,omitempty"`
TvSearch *RssSearch `xml:"tv-search,omitempty"`
MovieSearch *RssSearch `xml:"movie-search,omitempty"`
}
type RssSearch struct {
Available string `xml:"available,attr"`
SupportedParams string `xml:"supportedParams,attr,omitempty"`
}
type RssCategories struct {
XMLName xml.Name `xml:"categories"`
Category []*RssCategoryTorznab
}
type RssCategoryTorznab struct {
XMLName xml.Name `xml:"category"`
ID string `xml:"id,attr"`
Name string `xml:"name,attr"`
Subcat []*RssSubCat
Description string `xml:"description,attr,omitempty"`
}
type RssSubCat struct {
XMLName xml.Name `xml:"subcat"`
ID string `xml:"id"`
Name string `xml:"name"`
Description string `xml:"description,omitempty"`
}
type RssTorrent struct {
XMLName xml.Name `xml:"torrent"`
Xmlns string `xml:"xmlns,attr"`
FileName string `xml:"fileName,omitempty"`
ContentLength string `xml:"contentLength,omitempty"`
InfoHash string `xml:"infoHash,omitempty"`
MagnetURI string `xml:"magnetUri,omitempty"`
}
type RssTorznab struct {
XMLName xml.Name `xml:"torznab"`
Xmlns string `xml:"xmlns,attr"`
Type string `xml:"type,omitempty"`
Size string `xml:"size,omitempty"`
Files string `xml:"files,omitempty"`
Grabs string `xml:"grabs,omitempty"`
Tvdbid string `xml:"tvdbid,omitempty"`
Rageid string `xml:"rageid,omitempty"`
Tvmazeid string `xml:"tvmazeid,omitempty"`
Imdb string `xml:"imdb,omitempty"`
BannerURL string `xml:"bannerurl,omitempty"`
Infohash string `xml:"infohash,omitempty"`
MagnetURL string `xml:"magneturl,omitempty"`
Seeders string `xml:"seeders,omitempty"`
Leechers string `xml:"leechers,omitempty"`
Peers string `xml:"peers,omitempty"`
SeedType string `xml:"seedtype,omitempty"`
MinimumRatio string `xml:"minimumratio,omitempty"`
MinimumSeedTime string `xml:"minimumseedtime,omitempty"`
DownloadVolumeFactor string `xml:"downloadvolumefactor,omitempty"`
UploadVolumeFactor string `xml:"uploadvolumefactor,omitempty"`
}
// RssCategory is a category for rss item
type RssCategory struct {
XMLName xml.Name `xml:"category"`
Domain string `xml:"domain"`
}
type RssEnclosure struct {
//RSS 2.0
XMLName xml.Name `xml:"enclosure"`
URL string `xml:"url,attr"`
Length string `xml:"length,attr"`
Type string `xml:"type,attr"`
}
type Rss struct {
*feeds.Feed
}
// create a new RssItem with a generic Item struct's data
func newRssItem(i *feeds.Item) *RssItem {
item := &RssItem{
Title: i.Title,
Link: i.Link.Href,
Description: i.Description,
GUID: i.Id,
PubDate: anyTimeFormat(time.RFC1123Z, i.Created, i.Updated),
}
intLength, err := strconv.ParseInt(i.Link.Length, 10, 64)
if err == nil && (intLength > 0 || i.Link.Type != "") {
item.Enclosure = &RssEnclosure{URL: i.Link.Href, Type: i.Link.Type, Length: i.Link.Length}
}
if i.Author != nil {
item.Author = i.Author.Name
}
return item
}
// returns the first non-zero time formatted as a string or ""
func anyTimeFormat(format string, times ...time.Time) string {
for _, t := range times {
if !t.IsZero() {
return t.Format(format)
}
}
return ""
}
// RssFeed : create a new RssFeed with a generic Feed struct's data
func (r *Rss) RssFeed() *RssFeed {
pub := anyTimeFormat(time.RFC1123Z, r.Created, r.Updated)
build := anyTimeFormat(time.RFC1123Z, r.Updated)
author := ""
if r.Author != nil {
author = r.Author.Email
if len(r.Author.Name) > 0 {
author = fmt.Sprintf("%s (%s)", r.Author.Email, r.Author.Name)
}
}
channel := &RssFeed{
Title: r.Title,
Link: r.Link.Href,
Description: r.Description,
ManagingEditor: author,
PubDate: pub,
LastBuildDate: build,
Copyright: r.Copyright,
}
for _, i := range r.Items {
channel.Items = append(channel.Items, newRssItem(i))
}
return channel
}
// FeedXml : return an XML-Ready object for an Rss object
func (r *Rss) FeedXml() interface{} {
// only generate version 2.0 feeds for now
return r.RssFeed().FeedXml()
}
// FeedXml : return an XML-ready object for an RssFeed object
func (r *RssFeed) FeedXml() interface{} {
return &rssFeedXML{Version: "2.0", Encoding: "UTF-8", Channel: r}
}
// FeedXml : return an XML-ready object for an RssFeed object
func (r *RssCaps) FeedXml() interface{} {
return &rssFeedXML{Version: "2.0", Encoding: "UTF-8", Caps: r}
}