gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

json.go (5434B)


      1 package feeds
      2 
      3 import (
      4 	"encoding/json"
      5 	"strings"
      6 	"time"
      7 )
      8 
      9 const jsonFeedVersion = "https://jsonfeed.org/version/1"
     10 
     11 // JSONAuthor represents the author of the feed or of an individual item
     12 // in the feed
     13 type JSONAuthor struct {
     14 	Name   string `json:"name,omitempty"`
     15 	Url    string `json:"url,omitempty"`
     16 	Avatar string `json:"avatar,omitempty"`
     17 }
     18 
     19 // JSONAttachment represents a related resource. Podcasts, for instance, would
     20 // include an attachment that’s an audio or video file.
     21 type JSONAttachment struct {
     22 	Url      string        `json:"url,omitempty"`
     23 	MIMEType string        `json:"mime_type,omitempty"`
     24 	Title    string        `json:"title,omitempty"`
     25 	Size     int32         `json:"size,omitempty"`
     26 	Duration time.Duration `json:"duration_in_seconds,omitempty"`
     27 }
     28 
     29 // MarshalJSON implements the json.Marshaler interface.
     30 // The Duration field is marshaled in seconds, all other fields are marshaled
     31 // based upon the definitions in struct tags.
     32 func (a *JSONAttachment) MarshalJSON() ([]byte, error) {
     33 	type EmbeddedJSONAttachment JSONAttachment
     34 	return json.Marshal(&struct {
     35 		Duration float64 `json:"duration_in_seconds,omitempty"`
     36 		*EmbeddedJSONAttachment
     37 	}{
     38 		EmbeddedJSONAttachment: (*EmbeddedJSONAttachment)(a),
     39 		Duration:               a.Duration.Seconds(),
     40 	})
     41 }
     42 
     43 // UnmarshalJSON implements the json.Unmarshaler interface.
     44 // The Duration field is expected to be in seconds, all other field types
     45 // match the struct definition.
     46 func (a *JSONAttachment) UnmarshalJSON(data []byte) error {
     47 	type EmbeddedJSONAttachment JSONAttachment
     48 	var raw struct {
     49 		Duration float64 `json:"duration_in_seconds,omitempty"`
     50 		*EmbeddedJSONAttachment
     51 	}
     52 	raw.EmbeddedJSONAttachment = (*EmbeddedJSONAttachment)(a)
     53 
     54 	err := json.Unmarshal(data, &raw)
     55 	if err != nil {
     56 		return err
     57 	}
     58 
     59 	if raw.Duration > 0 {
     60 		nsec := int64(raw.Duration * float64(time.Second))
     61 		raw.EmbeddedJSONAttachment.Duration = time.Duration(nsec)
     62 	}
     63 
     64 	return nil
     65 }
     66 
     67 // JSONItem represents a single entry/post for the feed.
     68 type JSONItem struct {
     69 	Id            string           `json:"id"`
     70 	Url           string           `json:"url,omitempty"`
     71 	ExternalUrl   string           `json:"external_url,omitempty"`
     72 	Title         string           `json:"title,omitempty"`
     73 	ContentHTML   string           `json:"content_html,omitempty"`
     74 	ContentText   string           `json:"content_text,omitempty"`
     75 	Summary       string           `json:"summary,omitempty"`
     76 	Image         string           `json:"image,omitempty"`
     77 	BannerImage   string           `json:"banner_,omitempty"`
     78 	PublishedDate *time.Time       `json:"date_published,omitempty"`
     79 	ModifiedDate  *time.Time       `json:"date_modified,omitempty"`
     80 	Author        *JSONAuthor      `json:"author,omitempty"`
     81 	Tags          []string         `json:"tags,omitempty"`
     82 	Attachments   []JSONAttachment `json:"attachments,omitempty"`
     83 }
     84 
     85 // JSONHub describes an endpoint that can be used to subscribe to real-time
     86 // notifications from the publisher of this feed.
     87 type JSONHub struct {
     88 	Type string `json:"type"`
     89 	Url  string `json:"url"`
     90 }
     91 
     92 // JSONFeed represents a syndication feed in the JSON Feed Version 1 format.
     93 // Matching the specification found here: https://jsonfeed.org/version/1.
     94 type JSONFeed struct {
     95 	Version     string      `json:"version"`
     96 	Title       string      `json:"title"`
     97 	HomePageUrl string      `json:"home_page_url,omitempty"`
     98 	FeedUrl     string      `json:"feed_url,omitempty"`
     99 	Description string      `json:"description,omitempty"`
    100 	UserComment string      `json:"user_comment,omitempty"`
    101 	NextUrl     string      `json:"next_url,omitempty"`
    102 	Icon        string      `json:"icon,omitempty"`
    103 	Favicon     string      `json:"favicon,omitempty"`
    104 	Author      *JSONAuthor `json:"author,omitempty"`
    105 	Expired     *bool       `json:"expired,omitempty"`
    106 	Hubs        []*JSONItem `json:"hubs,omitempty"`
    107 	Items       []*JSONItem `json:"items,omitempty"`
    108 }
    109 
    110 // JSON is used to convert a generic Feed to a JSONFeed.
    111 type JSON struct {
    112 	*Feed
    113 }
    114 
    115 // ToJSON encodes f into a JSON string. Returns an error if marshalling fails.
    116 func (f *JSON) ToJSON() (string, error) {
    117 	return f.JSONFeed().ToJSON()
    118 }
    119 
    120 // ToJSON encodes f into a JSON string. Returns an error if marshalling fails.
    121 func (f *JSONFeed) ToJSON() (string, error) {
    122 	data, err := json.MarshalIndent(f, "", "  ")
    123 	if err != nil {
    124 		return "", err
    125 	}
    126 
    127 	return string(data), nil
    128 }
    129 
    130 // JSONFeed creates a new JSONFeed with a generic Feed struct's data.
    131 func (f *JSON) JSONFeed() *JSONFeed {
    132 	feed := &JSONFeed{
    133 		Version:     jsonFeedVersion,
    134 		Title:       f.Title,
    135 		Description: f.Description,
    136 	}
    137 
    138 	if f.Link != nil {
    139 		feed.HomePageUrl = f.Link.Href
    140 	}
    141 	if f.Author != nil {
    142 		feed.Author = &JSONAuthor{
    143 			Name: f.Author.Name,
    144 		}
    145 	}
    146 	for _, e := range f.Items {
    147 		feed.Items = append(feed.Items, newJSONItem(e))
    148 	}
    149 	return feed
    150 }
    151 
    152 func newJSONItem(i *Item) *JSONItem {
    153 	item := &JSONItem{
    154 		Id:      i.Id,
    155 		Title:   i.Title,
    156 		Summary: i.Description,
    157 
    158 		ContentHTML: i.Content,
    159 	}
    160 
    161 	if i.Link != nil {
    162 		item.Url = i.Link.Href
    163 	}
    164 	if i.Source != nil {
    165 		item.ExternalUrl = i.Source.Href
    166 	}
    167 	if i.Author != nil {
    168 		item.Author = &JSONAuthor{
    169 			Name: i.Author.Name,
    170 		}
    171 	}
    172 	if !i.Created.IsZero() {
    173 		item.PublishedDate = &i.Created
    174 	}
    175 	if !i.Updated.IsZero() {
    176 		item.ModifiedDate = &i.Updated
    177 	}
    178 	if i.Enclosure != nil && strings.HasPrefix(i.Enclosure.Type, "image/") {
    179 		item.Image = i.Enclosure.Url
    180 	}
    181 
    182 	return item
    183 }