mirror of https://github.com/gorilla/feeds
Mirror of https://github.com/gorilla/feeds
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
190 lines
5.7 KiB
190 lines
5.7 KiB
package feeds |
|
|
|
import ( |
|
"encoding/json" |
|
"strings" |
|
"time" |
|
) |
|
|
|
const jsonFeedVersion = "https://jsonfeed.org/version/1.1" |
|
|
|
// JSONAuthor represents the author of the feed or of an individual item |
|
// in the feed |
|
type JSONAuthor struct { |
|
Name string `json:"name,omitempty"` |
|
Url string `json:"url,omitempty"` |
|
Avatar string `json:"avatar,omitempty"` |
|
} |
|
|
|
// JSONAttachment represents a related resource. Podcasts, for instance, would |
|
// include an attachment that’s an audio or video file. |
|
type JSONAttachment struct { |
|
Url string `json:"url,omitempty"` |
|
MIMEType string `json:"mime_type,omitempty"` |
|
Title string `json:"title,omitempty"` |
|
Size int32 `json:"size,omitempty"` |
|
Duration time.Duration `json:"duration_in_seconds,omitempty"` |
|
} |
|
|
|
// MarshalJSON implements the json.Marshaler interface. |
|
// The Duration field is marshaled in seconds, all other fields are marshaled |
|
// based upon the definitions in struct tags. |
|
func (a *JSONAttachment) MarshalJSON() ([]byte, error) { |
|
type EmbeddedJSONAttachment JSONAttachment |
|
return json.Marshal(&struct { |
|
Duration float64 `json:"duration_in_seconds,omitempty"` |
|
*EmbeddedJSONAttachment |
|
}{ |
|
EmbeddedJSONAttachment: (*EmbeddedJSONAttachment)(a), |
|
Duration: a.Duration.Seconds(), |
|
}) |
|
} |
|
|
|
// UnmarshalJSON implements the json.Unmarshaler interface. |
|
// The Duration field is expected to be in seconds, all other field types |
|
// match the struct definition. |
|
func (a *JSONAttachment) UnmarshalJSON(data []byte) error { |
|
type EmbeddedJSONAttachment JSONAttachment |
|
var raw struct { |
|
Duration float64 `json:"duration_in_seconds,omitempty"` |
|
*EmbeddedJSONAttachment |
|
} |
|
raw.EmbeddedJSONAttachment = (*EmbeddedJSONAttachment)(a) |
|
|
|
err := json.Unmarshal(data, &raw) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
if raw.Duration > 0 { |
|
nsec := int64(raw.Duration * float64(time.Second)) |
|
raw.EmbeddedJSONAttachment.Duration = time.Duration(nsec) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// JSONItem represents a single entry/post for the feed. |
|
type JSONItem struct { |
|
Id string `json:"id"` |
|
Url string `json:"url,omitempty"` |
|
ExternalUrl string `json:"external_url,omitempty"` |
|
Title string `json:"title,omitempty"` |
|
ContentHTML string `json:"content_html,omitempty"` |
|
ContentText string `json:"content_text,omitempty"` |
|
Summary string `json:"summary,omitempty"` |
|
Image string `json:"image,omitempty"` |
|
BannerImage string `json:"banner_,omitempty"` |
|
PublishedDate *time.Time `json:"date_published,omitempty"` |
|
ModifiedDate *time.Time `json:"date_modified,omitempty"` |
|
Author *JSONAuthor `json:"author,omitempty"` // deprecated in JSON Feed v1.1, keeping for backwards compatibility |
|
Authors []*JSONAuthor `json:"authors,omitempty"` |
|
Tags []string `json:"tags,omitempty"` |
|
Attachments []JSONAttachment `json:"attachments,omitempty"` |
|
} |
|
|
|
// JSONHub describes an endpoint that can be used to subscribe to real-time |
|
// notifications from the publisher of this feed. |
|
type JSONHub struct { |
|
Type string `json:"type"` |
|
Url string `json:"url"` |
|
} |
|
|
|
// JSONFeed represents a syndication feed in the JSON Feed Version 1 format. |
|
// Matching the specification found here: https://jsonfeed.org/version/1. |
|
type JSONFeed struct { |
|
Version string `json:"version"` |
|
Title string `json:"title"` |
|
Language string `json:"language,omitempty"` |
|
HomePageUrl string `json:"home_page_url,omitempty"` |
|
FeedUrl string `json:"feed_url,omitempty"` |
|
Description string `json:"description,omitempty"` |
|
UserComment string `json:"user_comment,omitempty"` |
|
NextUrl string `json:"next_url,omitempty"` |
|
Icon string `json:"icon,omitempty"` |
|
Favicon string `json:"favicon,omitempty"` |
|
Author *JSONAuthor `json:"author,omitempty"` // deprecated in JSON Feed v1.1, keeping for backwards compatibility |
|
Authors []*JSONAuthor `json:"authors,omitempty"` |
|
Expired *bool `json:"expired,omitempty"` |
|
Hubs []*JSONHub `json:"hubs,omitempty"` |
|
Items []*JSONItem `json:"items,omitempty"` |
|
} |
|
|
|
// JSON is used to convert a generic Feed to a JSONFeed. |
|
type JSON struct { |
|
*Feed |
|
} |
|
|
|
// ToJSON encodes f into a JSON string. Returns an error if marshalling fails. |
|
func (f *JSON) ToJSON() (string, error) { |
|
return f.JSONFeed().ToJSON() |
|
} |
|
|
|
// ToJSON encodes f into a JSON string. Returns an error if marshalling fails. |
|
func (f *JSONFeed) ToJSON() (string, error) { |
|
data, err := json.MarshalIndent(f, "", " ") |
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
return string(data), nil |
|
} |
|
|
|
// JSONFeed creates a new JSONFeed with a generic Feed struct's data. |
|
func (f *JSON) JSONFeed() *JSONFeed { |
|
feed := &JSONFeed{ |
|
Version: jsonFeedVersion, |
|
Title: f.Title, |
|
Description: f.Description, |
|
} |
|
|
|
if f.Link != nil { |
|
feed.HomePageUrl = f.Link.Href |
|
} |
|
if f.Author != nil { |
|
author := &JSONAuthor{ |
|
Name: f.Author.Name, |
|
} |
|
feed.Author = author |
|
feed.Authors = []*JSONAuthor{author} |
|
} |
|
for _, e := range f.Items { |
|
feed.Items = append(feed.Items, newJSONItem(e)) |
|
} |
|
return feed |
|
} |
|
|
|
func newJSONItem(i *Item) *JSONItem { |
|
item := &JSONItem{ |
|
Id: i.Id, |
|
Title: i.Title, |
|
Summary: i.Description, |
|
|
|
ContentHTML: i.Content, |
|
} |
|
|
|
if i.Link != nil { |
|
item.Url = i.Link.Href |
|
} |
|
if i.Source != nil { |
|
item.ExternalUrl = i.Source.Href |
|
} |
|
if i.Author != nil { |
|
author := &JSONAuthor{ |
|
Name: i.Author.Name, |
|
} |
|
item.Author = author |
|
item.Authors = []*JSONAuthor{author} |
|
} |
|
if !i.Created.IsZero() { |
|
item.PublishedDate = &i.Created |
|
} |
|
if !i.Updated.IsZero() { |
|
item.ModifiedDate = &i.Updated |
|
} |
|
if i.Enclosure != nil && strings.HasPrefix(i.Enclosure.Type, "image/") { |
|
item.Image = i.Enclosure.Url |
|
} |
|
|
|
return item |
|
}
|
|
|