gotosocial/vendor/github.com/gorilla/feeds/json.go
tobi 80663061d8
[feature] Add opt-in RSS feed for account's latest Public posts (#897)
* start adding rss functionality

* add gorilla/feeds dependency

* first bash at building rss feed
still needs work, this is an interim commit

* tidy up a bit

* add publicOnly option to GetAccountLastPosted

* implement rss endpoint

* fix test

* add initial user docs for rss

* update rss logo

* docs update

* add rssFeed to frontend

* feed -> feed.rss

* enableRSS

* increase rss logo size a lil bit

* add rss toggle

* move emojify to text package

* fiddle with rss feed formatting

* add Text field to test statuses

* move status to rss item to typeconverter

* update bun schema for enablerss

* simplify 304 checking

* assume account not rss

* update tests

* update swagger docs

* allow more characters in title, trim nicer

* update last posted to be more consistent
2022-10-08 14:00:39 +02:00

183 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package feeds
import (
"encoding/json"
"strings"
"time"
)
const jsonFeedVersion = "https://jsonfeed.org/version/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 thats 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"`
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"`
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"`
Expired *bool `json:"expired,omitempty"`
Hubs []*JSONItem `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 {
feed.Author = &JSONAuthor{
Name: f.Author.Name,
}
}
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 {
item.Author = &JSONAuthor{
Name: i.Author.Name,
}
}
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
}