aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Frédéric Guillot <fred@miniflux.net>2019-11-28 23:47:53 -0800
committerGravatar Frédéric Guillot <fred@miniflux.net>2019-11-28 23:55:40 -0800
commit912a98788e66b836125fe8ef37672a4de20c169c (patch)
tree857e4c57695139ee07193223beae7d04775e3347
parentf90e9dfab015a2ebdbbe66334a29477152a948f5 (diff)
Add support of media elements for Atom feeds
-rw-r--r--reader/atom/atom.go69
-rw-r--r--reader/atom/parser_test.go171
-rw-r--r--reader/media/media.go176
-rw-r--r--reader/media/media_test.go110
-rw-r--r--reader/rss/parser_test.go49
-rw-r--r--reader/rss/rss.go96
6 files changed, 569 insertions, 102 deletions
diff --git a/reader/atom/atom.go b/reader/atom/atom.go
index 30297e5..517b43d 100644
--- a/reader/atom/atom.go
+++ b/reader/atom/atom.go
@@ -15,6 +15,7 @@ import (
"miniflux.app/logger"
"miniflux.app/model"
"miniflux.app/reader/date"
+ "miniflux.app/reader/media"
"miniflux.app/reader/sanitizer"
"miniflux.app/url"
)
@@ -29,15 +30,15 @@ type atomFeed struct {
}
type atomEntry struct {
- ID string `xml:"id"`
- Title atomContent `xml:"title"`
- Published string `xml:"published"`
- Updated string `xml:"updated"`
- Links []atomLink `xml:"link"`
- Summary atomContent `xml:"summary"`
- Content atomContent `xml:"content"`
- MediaGroup atomMediaGroup `xml:"http://search.yahoo.com/mrss/ group"`
- Author atomAuthor `xml:"author"`
+ ID string `xml:"id"`
+ Title atomContent `xml:"title"`
+ Published string `xml:"published"`
+ Updated string `xml:"updated"`
+ Links []atomLink `xml:"link"`
+ Summary atomContent `xml:"summary"`
+ Content atomContent `xml:"http://www.w3.org/2005/Atom content"`
+ Author atomAuthor `xml:"author"`
+ media.Element
}
type atomAuthor struct {
@@ -58,10 +59,6 @@ type atomContent struct {
XML string `xml:",innerxml"`
}
-type atomMediaGroup struct {
- Description string `xml:"http://search.yahoo.com/mrss/ description"`
-}
-
func (a *atomFeed) Transform() *model.Feed {
feed := new(model.Feed)
feed.FeedURL = getRelationURL(a.Links, "self")
@@ -179,8 +176,9 @@ func getContent(a *atomEntry) string {
return r
}
- if a.MediaGroup.Description != "" {
- return a.MediaGroup.Description
+ mediaDescription := a.FirstMediaDescription()
+ if mediaDescription != "" {
+ return mediaDescription
}
return ""
@@ -203,11 +201,48 @@ func getHash(a *atomEntry) string {
func getEnclosures(a *atomEntry) model.EnclosureList {
enclosures := make(model.EnclosureList, 0)
+ duplicates := make(map[string]bool, 0)
+
+ for _, mediaThumbnail := range a.AllMediaThumbnails() {
+ if _, found := duplicates[mediaThumbnail.URL]; !found {
+ duplicates[mediaThumbnail.URL] = true
+ enclosures = append(enclosures, &model.Enclosure{
+ URL: mediaThumbnail.URL,
+ MimeType: mediaThumbnail.MimeType(),
+ Size: mediaThumbnail.Size(),
+ })
+ }
+ }
for _, link := range a.Links {
if strings.ToLower(link.Rel) == "enclosure" {
- length, _ := strconv.ParseInt(link.Length, 10, 0)
- enclosures = append(enclosures, &model.Enclosure{URL: link.URL, MimeType: link.Type, Size: length})
+ if _, found := duplicates[link.URL]; !found {
+ duplicates[link.URL] = true
+ length, _ := strconv.ParseInt(link.Length, 10, 0)
+ enclosures = append(enclosures, &model.Enclosure{URL: link.URL, MimeType: link.Type, Size: length})
+ }
+ }
+ }
+
+ for _, mediaContent := range a.AllMediaContents() {
+ if _, found := duplicates[mediaContent.URL]; !found {
+ duplicates[mediaContent.URL] = true
+ enclosures = append(enclosures, &model.Enclosure{
+ URL: mediaContent.URL,
+ MimeType: mediaContent.MimeType(),
+ Size: mediaContent.Size(),
+ })
+ }
+ }
+
+ for _, mediaPeerLink := range a.AllMediaPeerLinks() {
+ if _, found := duplicates[mediaPeerLink.URL]; !found {
+ duplicates[mediaPeerLink.URL] = true
+ enclosures = append(enclosures, &model.Enclosure{
+ URL: mediaPeerLink.URL,
+ MimeType: mediaPeerLink.MimeType(),
+ Size: mediaPeerLink.Size(),
+ })
}
}
diff --git a/reader/atom/parser_test.go b/reader/atom/parser_test.go
index 746c767..37ab32e 100644
--- a/reader/atom/parser_test.go
+++ b/reader/atom/parser_test.go
@@ -472,31 +472,30 @@ func TestParseEntryWithEnclosures(t *testing.T) {
}
if len(feed.Entries[0].Enclosures) != 2 {
- t.Errorf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures))
+ t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures))
}
- if feed.Entries[0].Enclosures[0].URL != "http://www.example.org/myaudiofile.mp3" {
- t.Errorf("Incorrect enclosure URL, got: %s", feed.Entries[0].Enclosures[0].URL)
+ expectedResults := []struct {
+ url string
+ mimeType string
+ size int64
+ }{
+ {"http://www.example.org/myaudiofile.mp3", "audio/mpeg", 1234},
+ {"http://www.example.org/myaudiofile.torrent", "application/x-bittorrent", 4567},
}
- if feed.Entries[0].Enclosures[0].MimeType != "audio/mpeg" {
- t.Errorf("Incorrect enclosure type, got: %s", feed.Entries[0].Enclosures[0].MimeType)
- }
-
- if feed.Entries[0].Enclosures[0].Size != 1234 {
- t.Errorf("Incorrect enclosure length, got: %d", feed.Entries[0].Enclosures[0].Size)
- }
-
- if feed.Entries[0].Enclosures[1].URL != "http://www.example.org/myaudiofile.torrent" {
- t.Errorf("Incorrect enclosure URL, got: %s", feed.Entries[0].Enclosures[1].URL)
- }
+ for index, enclosure := range feed.Entries[0].Enclosures {
+ if expectedResults[index].url != enclosure.URL {
+ t.Errorf(`Unexpected enclosure URL, got %q instead of %q`, enclosure.URL, expectedResults[index].url)
+ }
- if feed.Entries[0].Enclosures[1].MimeType != "application/x-bittorrent" {
- t.Errorf("Incorrect enclosure type, got: %s", feed.Entries[0].Enclosures[1].MimeType)
- }
+ if expectedResults[index].mimeType != enclosure.MimeType {
+ t.Errorf(`Unexpected enclosure type, got %q instead of %q`, enclosure.MimeType, expectedResults[index].mimeType)
+ }
- if feed.Entries[0].Enclosures[1].Size != 4567 {
- t.Errorf("Incorrect enclosure length, got: %d", feed.Entries[0].Enclosures[1].Size)
+ if expectedResults[index].size != enclosure.Size {
+ t.Errorf(`Unexpected enclosure size, got %d instead of %d`, enclosure.Size, expectedResults[index].size)
+ }
}
}
@@ -596,3 +595,137 @@ func TestParseWithInvalidCharacterEntity(t *testing.T) {
t.Errorf(`Incorrect URL, got: %q`, feed.SiteURL)
}
}
+
+func TestParseMediaGroup(t *testing.T) {
+ data := `<?xml version="1.0" encoding="utf-8"?>
+ <feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
+ <id>http://www.example.org/myfeed</id>
+ <title>My Video Feed</title>
+ <updated>2005-07-15T12:00:00Z</updated>
+ <link href="http://example.org" />
+ <link rel="self" href="http://example.org/myfeed" />
+ <entry>
+ <id>http://www.example.org/entries/1</id>
+ <title>Some Video</title>
+ <updated>2005-07-15T12:00:00Z</updated>
+ <link href="http://www.example.org/entries/1" />
+ <media:group>
+ <media:title>Another title</media:title>
+ <media:content url="https://www.youtube.com/v/abcd" type="application/x-shockwave-flash" width="640" height="390"/>
+ <media:thumbnail url="https://example.org/thumbnail.jpg" width="480" height="360"/>
+ <media:description>Some description
+A website: http://example.org/</media:description>
+ </media:group>
+ </entry>
+ </feed>`
+
+ feed, err := Parse(bytes.NewBufferString(data))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(feed.Entries) != 1 {
+ t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
+ }
+
+ if feed.Entries[0].URL != "http://www.example.org/entries/1" {
+ t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL)
+ }
+
+ if feed.Entries[0].Content != `Some description<br>A website: <a href="http://example.org/">http://example.org/</a>` {
+ t.Errorf("Incorrect entry content, got: %q", feed.Entries[0].Content)
+ }
+
+ if len(feed.Entries[0].Enclosures) != 2 {
+ t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures))
+ }
+
+ expectedResults := []struct {
+ url string
+ mimeType string
+ size int64
+ }{
+ {"https://example.org/thumbnail.jpg", "image/*", 0},
+ {"https://www.youtube.com/v/abcd", "application/x-shockwave-flash", 0},
+ }
+
+ for index, enclosure := range feed.Entries[0].Enclosures {
+ if expectedResults[index].url != enclosure.URL {
+ t.Errorf(`Unexpected enclosure URL, got %q instead of %q`, enclosure.URL, expectedResults[index].url)
+ }
+
+ if expectedResults[index].mimeType != enclosure.MimeType {
+ t.Errorf(`Unexpected enclosure type, got %q instead of %q`, enclosure.MimeType, expectedResults[index].mimeType)
+ }
+
+ if expectedResults[index].size != enclosure.Size {
+ t.Errorf(`Unexpected enclosure size, got %d instead of %d`, enclosure.Size, expectedResults[index].size)
+ }
+ }
+}
+
+func TestParseMediaElements(t *testing.T) {
+ data := `<?xml version="1.0" encoding="utf-8"?>
+ <feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
+ <id>http://www.example.org/myfeed</id>
+ <title>My Video Feed</title>
+ <updated>2005-07-15T12:00:00Z</updated>
+ <link href="http://example.org" />
+ <link rel="self" href="http://example.org/myfeed" />
+ <entry>
+ <id>http://www.example.org/entries/1</id>
+ <title>Some Video</title>
+ <updated>2005-07-15T12:00:00Z</updated>
+ <link href="http://www.example.org/entries/1" />
+ <media:title>Another title</media:title>
+ <media:content url="https://www.youtube.com/v/abcd" type="application/x-shockwave-flash" width="640" height="390"/>
+ <media:thumbnail url="https://example.org/thumbnail.jpg" width="480" height="360"/>
+ <media:description>Some description
+A website: http://example.org/</media:description>
+ </entry>
+ </feed>`
+
+ feed, err := Parse(bytes.NewBufferString(data))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(feed.Entries) != 1 {
+ t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
+ }
+
+ if feed.Entries[0].URL != "http://www.example.org/entries/1" {
+ t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL)
+ }
+
+ if feed.Entries[0].Content != `Some description<br>A website: <a href="http://example.org/">http://example.org/</a>` {
+ t.Errorf("Incorrect entry content, got: %q", feed.Entries[0].Content)
+ }
+
+ if len(feed.Entries[0].Enclosures) != 2 {
+ t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures))
+ }
+
+ expectedResults := []struct {
+ url string
+ mimeType string
+ size int64
+ }{
+ {"https://example.org/thumbnail.jpg", "image/*", 0},
+ {"https://www.youtube.com/v/abcd", "application/x-shockwave-flash", 0},
+ }
+
+ for index, enclosure := range feed.Entries[0].Enclosures {
+ if expectedResults[index].url != enclosure.URL {
+ t.Errorf(`Unexpected enclosure URL, got %q instead of %q`, enclosure.URL, expectedResults[index].url)
+ }
+
+ if expectedResults[index].mimeType != enclosure.MimeType {
+ t.Errorf(`Unexpected enclosure type, got %q instead of %q`, enclosure.MimeType, expectedResults[index].mimeType)
+ }
+
+ if expectedResults[index].size != enclosure.Size {
+ t.Errorf(`Unexpected enclosure size, got %d instead of %d`, enclosure.Size, expectedResults[index].size)
+ }
+ }
+}
diff --git a/reader/media/media.go b/reader/media/media.go
new file mode 100644
index 0000000..d414191
--- /dev/null
+++ b/reader/media/media.go
@@ -0,0 +1,176 @@
+// Copyright 2019 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package media // import "miniflux.app/reader/media"
+
+import (
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+var textLinkRegex = regexp.MustCompile(`(?mi)(\bhttps?:\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])`)
+
+// Element represents XML media elements.
+type Element struct {
+ MediaGroups []Group `xml:"http://search.yahoo.com/mrss/ group"`
+ MediaContents []Content `xml:"http://search.yahoo.com/mrss/ content"`
+ MediaThumbnails []Thumbnail `xml:"http://search.yahoo.com/mrss/ thumbnail"`
+ MediaDescriptions DescriptionList `xml:"http://search.yahoo.com/mrss/ description"`
+ MediaPeerLinks []PeerLink `xml:"http://search.yahoo.com/mrss/ peerLink"`
+}
+
+// AllMediaThumbnails returns all thumbnail elements merged together.
+func (e *Element) AllMediaThumbnails() []Thumbnail {
+ var items []Thumbnail
+ items = append(items, e.MediaThumbnails...)
+ for _, mediaGroup := range e.MediaGroups {
+ items = append(items, mediaGroup.MediaThumbnails...)
+ }
+ return items
+}
+
+// AllMediaContents returns all content elements merged together.
+func (e *Element) AllMediaContents() []Content {
+ var items []Content
+ items = append(items, e.MediaContents...)
+ for _, mediaGroup := range e.MediaGroups {
+ items = append(items, mediaGroup.MediaContents...)
+ }
+ return items
+}
+
+// AllMediaPeerLinks returns all peer link elements merged together.
+func (e *Element) AllMediaPeerLinks() []PeerLink {
+ var items []PeerLink
+ items = append(items, e.MediaPeerLinks...)
+ for _, mediaGroup := range e.MediaGroups {
+ items = append(items, mediaGroup.MediaPeerLinks...)
+ }
+ return items
+}
+
+// FirstMediaDescription returns the first description element.
+func (e *Element) FirstMediaDescription() string {
+ description := e.MediaDescriptions.First()
+ if description != "" {
+ return description
+ }
+
+ for _, mediaGroup := range e.MediaGroups {
+ description = mediaGroup.MediaDescriptions.First()
+ if description != "" {
+ return description
+ }
+ }
+
+ return ""
+}
+
+// Group represents a XML element "media:group".
+type Group struct {
+ MediaContents []Content `xml:"http://search.yahoo.com/mrss/ content"`
+ MediaThumbnails []Thumbnail `xml:"http://search.yahoo.com/mrss/ thumbnail"`
+ MediaDescriptions DescriptionList `xml:"http://search.yahoo.com/mrss/ description"`
+ MediaPeerLinks []PeerLink `xml:"http://search.yahoo.com/mrss/ peerLink"`
+}
+
+// Content represents a XML element "media:content".
+type Content struct {
+ URL string `xml:"url,attr"`
+ Type string `xml:"type,attr"`
+ FileSize string `xml:"fileSize,attr"`
+ Medium string `xml:"medium,attr"`
+}
+
+// MimeType returns the attachment mime type.
+func (mc *Content) MimeType() string {
+ switch {
+ case mc.Type == "" && mc.Medium == "image":
+ return "image/*"
+ case mc.Type == "" && mc.Medium == "video":
+ return "video/*"
+ case mc.Type == "" && mc.Medium == "audio":
+ return "audio/*"
+ case mc.Type == "" && mc.Medium == "video":
+ return "video/*"
+ case mc.Type != "":
+ return mc.Type
+ default:
+ return "application/octet-stream"
+ }
+}
+
+// Size returns the attachment size.
+func (mc *Content) Size() int64 {
+ if mc.FileSize == "" {
+ return 0
+ }
+ size, _ := strconv.ParseInt(mc.FileSize, 10, 0)
+ return size
+}
+
+// Thumbnail represents a XML element "media:thumbnail".
+type Thumbnail struct {
+ URL string `xml:"url,attr"`
+}
+
+// MimeType returns the attachment mime type.
+func (t *Thumbnail) MimeType() string {
+ return "image/*"
+}
+
+// Size returns the attachment size.
+func (t *Thumbnail) Size() int64 {
+ return 0
+}
+
+// PeerLink represents a XML element "media:peerLink".
+type PeerLink struct {
+ URL string `xml:"href,attr"`
+ Type string `xml:"type,attr"`
+}
+
+// MimeType returns the attachment mime type.
+func (p *PeerLink) MimeType() string {
+ if p.Type != "" {
+ return p.Type
+ }
+ return "application/octet-stream"
+}
+
+// Size returns the attachment size.
+func (p *PeerLink) Size() int64 {
+ return 0
+}
+
+// Description represents a XML element "media:description".
+type Description struct {
+ Type string `xml:"type,attr"`
+ Description string `xml:",chardata"`
+}
+
+// HTML returns the description as HTML.
+func (d *Description) HTML() string {
+ if d.Type == "html" {
+ return d.Description
+ }
+
+ content := strings.Replace(d.Description, "\n", "<br>", -1)
+ return textLinkRegex.ReplaceAllString(content, `<a href="${1}">${1}</a>`)
+}
+
+// DescriptionList represents a list of "media:description" XML elements.
+type DescriptionList []Description
+
+// First returns the first non-empty description.
+func (dl DescriptionList) First() string {
+ for _, description := range dl {
+ contents := description.HTML()
+ if contents != "" {
+ return contents
+ }
+ }
+ return ""
+}
diff --git a/reader/media/media_test.go b/reader/media/media_test.go
new file mode 100644
index 0000000..b0d2842
--- /dev/null
+++ b/reader/media/media_test.go
@@ -0,0 +1,110 @@
+// Copyright 2019 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package media // import "miniflux.app/reader/media"
+
+import "testing"
+
+func TestContentMimeType(t *testing.T) {
+ scenarios := []struct {
+ inputType, inputMedium, expectedMimeType string
+ }{
+ {"image/png", "image", "image/png"},
+ {"", "image", "image/*"},
+ {"", "video", "video/*"},
+ {"", "audio", "audio/*"},
+ {"", "", "application/octet-stream"},
+ }
+
+ for _, scenario := range scenarios {
+ content := &Content{Type: scenario.inputType, Medium: scenario.inputMedium}
+ result := content.MimeType()
+ if result != scenario.expectedMimeType {
+ t.Errorf(`Unexpected mime type, got %q instead of %q for type=%q medium=%q`,
+ result,
+ scenario.expectedMimeType,
+ scenario.inputType,
+ scenario.inputMedium,
+ )
+ }
+ }
+}
+
+func TestContentSize(t *testing.T) {
+ scenarios := []struct {
+ inputSize string
+ expectedSize int64
+ }{
+ {"", 0},
+ {"123", int64(123)},
+ }
+
+ for _, scenario := range scenarios {
+ content := &Content{FileSize: scenario.inputSize}
+ result := content.Size()
+ if result != scenario.expectedSize {
+ t.Errorf(`Unexpected size, got %d instead of %d for %q`,
+ result,
+ scenario.expectedSize,
+ scenario.inputSize,
+ )
+ }
+ }
+}
+
+func TestPeerLinkType(t *testing.T) {
+ scenarios := []struct {
+ inputType string
+ expectedMimeType string
+ }{
+ {"", "application/octet-stream"},
+ {"application/x-bittorrent", "application/x-bittorrent"},
+ }
+
+ for _, scenario := range scenarios {
+ peerLink := &PeerLink{Type: scenario.inputType}
+ result := peerLink.MimeType()
+ if result != scenario.expectedMimeType {
+ t.Errorf(`Unexpected mime type, got %q instead of %q for %q`,
+ result,
+ scenario.expectedMimeType,
+ scenario.inputType,
+ )
+ }
+ }
+}
+
+func TestDescription(t *testing.T) {
+ scenarios := []struct {
+ inputType string
+ inputContent string
+ expectedDescription string
+ }{
+ {"", "", ""},
+ {"html", "a <b>c</b>", "a <b>c</b>"},
+ {"plain", "a\nhttp://www.example.org/", `a<br><a href="http://www.example.org/">http://www.example.org/</a>`},
+ }
+
+ for _, scenario := range scenarios {
+ desc := &Description{Type: scenario.inputType, Description: scenario.inputContent}
+ result := desc.HTML()
+ if result != scenario.expectedDescription {
+ t.Errorf(`Unexpected description, got %q instead of %q for %q`,
+ result,
+ scenario.expectedDescription,
+ scenario.inputType,
+ )
+ }
+ }
+}
+
+func TestFirstDescription(t *testing.T) {
+ var descList DescriptionList
+ descList = append(descList, Description{})
+ descList = append(descList, Description{Description: "Something"})
+
+ if descList.First() != "Something" {
+ t.Errorf(`Unexpected description`)
+ }
+}
diff --git a/reader/rss/parser_test.go b/reader/rss/parser_test.go
index af98d7e..56ae159 100644
--- a/reader/rss/parser_test.go
+++ b/reader/rss/parser_test.go
@@ -771,3 +771,52 @@ func TestParseEntryWithMediaContent(t *testing.T) {
}
}
}
+
+func TestParseEntryWithMediaPeerLink(t *testing.T) {
+ data := `<?xml version="1.0" encoding="utf-8"?>
+ <rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
+ <channel>
+ <title>My Example Feed</title>
+ <link>http://example.org</link>
+ <item>
+ <title>Example Item</title>
+ <link>http://www.example.org/entries/1</link>
+ <media:peerLink type="application/x-bittorrent" href="http://www.example.org/file.torrent" />
+ </item>
+ </channel>
+ </rss>`
+
+ feed, err := Parse(bytes.NewBufferString(data))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(feed.Entries) != 1 {
+ t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
+ }
+ if len(feed.Entries[0].Enclosures) != 1 {
+ t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures))
+ }
+
+ expectedResults := []struct {
+ url string
+ mimeType string
+ size int64
+ }{
+ {"http://www.example.org/file.torrent", "application/x-bittorrent", 0},
+ }
+
+ for index, enclosure := range feed.Entries[0].Enclosures {
+ if expectedResults[index].url != enclosure.URL {
+ t.Errorf(`Unexpected enclosure URL, got %q instead of %q`, enclosure.URL, expectedResults[index].url)
+ }
+
+ if expectedResults[index].mimeType != enclosure.MimeType {
+ t.Errorf(`Unexpected enclosure type, got %q instead of %q`, enclosure.MimeType, expectedResults[index].mimeType)
+ }
+
+ if expectedResults[index].size != enclosure.Size {
+ t.Errorf(`Unexpected enclosure size, got %d instead of %d`, enclosure.Size, expectedResults[index].size)
+ }
+ }
+}
diff --git a/reader/rss/rss.go b/reader/rss/rss.go
index 3803798..fb0c5bf 100644
--- a/reader/rss/rss.go
+++ b/reader/rss/rss.go
@@ -15,6 +15,7 @@ import (
"miniflux.app/logger"
"miniflux.app/model"
"miniflux.app/reader/date"
+ "miniflux.app/reader/media"
"miniflux.app/reader/sanitizer"
"miniflux.app/url"
)
@@ -65,62 +66,20 @@ func (enclosure *rssEnclosure) Size() int64 {
}
type rssItem struct {
- GUID string `xml:"guid"`
- Title string `xml:"title"`
- Links []rssLink `xml:"link"`
- OriginalLink string `xml:"http://rssnamespace.org/feedburner/ext/1.0 origLink"`
- CommentLinks []rssCommentLink `xml:"comments"`
- Description string `xml:"description"`
- EncodedContent string `xml:"http://purl.org/rss/1.0/modules/content/ encoded"`
- PubDate string `xml:"pubDate"`
- Date string `xml:"http://purl.org/dc/elements/1.1/ date"`
- Authors []rssAuthor `xml:"author"`
- Creator string `xml:"http://purl.org/dc/elements/1.1/ creator"`
- EnclosureLinks []rssEnclosure `xml:"enclosure"`
- OrigEnclosureLink string `xml:"http://rssnamespace.org/feedburner/ext/1.0 origEnclosureLink"`
- MediaGroup []rssMediaGroup `xml:"http://search.yahoo.com/mrss/ group"`
- MediaContents []rssMediaContent `xml:"http://search.yahoo.com/mrss/ content"`
- MediaThumbnails []rssMediaThumbnails `xml:"http://search.yahoo.com/mrss/ thumbnail"`
-}
-
-type rssMediaGroup struct {
- MediaList []rssMediaContent `xml:"content"`
-}
-
-type rssMediaContent struct {
- URL string `xml:"url,attr"`
- Type string `xml:"type,attr"`
- FileSize string `xml:"fileSize,attr"`
- Medium string `xml:"medium,attr"`
-}
-
-func (mediaContent *rssMediaContent) MimeType() string {
- switch {
- case mediaContent.Type == "" && mediaContent.Medium == "image":
- return "image/*"
- case mediaContent.Type == "" && mediaContent.Medium == "video":
- return "video/*"
- case mediaContent.Type == "" && mediaContent.Medium == "audio":
- return "audio/*"
- case mediaContent.Type == "" && mediaContent.Medium == "video":
- return "video/*"
- case mediaContent.Type != "":
- return mediaContent.Type
- default:
- return "application/octet-stream"
- }
-}
-
-func (mediaContent *rssMediaContent) Size() int64 {
- if mediaContent.FileSize == "" {
- return 0
- }
- size, _ := strconv.ParseInt(mediaContent.FileSize, 10, 0)
- return size
-}
-
-type rssMediaThumbnails struct {
- URL string `xml:"url,attr"`
+ GUID string `xml:"guid"`
+ Title string `xml:"title"`
+ Links []rssLink `xml:"link"`
+ OriginalLink string `xml:"http://rssnamespace.org/feedburner/ext/1.0 origLink"`
+ CommentLinks []rssCommentLink `xml:"comments"`
+ Description string `xml:"description"`
+ EncodedContent string `xml:"http://purl.org/rss/1.0/modules/content/ encoded"`
+ PubDate string `xml:"pubDate"`
+ Date string `xml:"http://purl.org/dc/elements/1.1/ date"`
+ Authors []rssAuthor `xml:"author"`
+ Creator string `xml:"http://purl.org/dc/elements/1.1/ creator"`
+ EnclosureLinks []rssEnclosure `xml:"enclosure"`
+ OrigEnclosureLink string `xml:"http://rssnamespace.org/feedburner/ext/1.0 origEnclosureLink"`
+ media.Element
}
func (r *rssFeed) SiteURL() string {
@@ -253,13 +212,13 @@ func (r *rssItem) Enclosures() model.EnclosureList {
enclosures := make(model.EnclosureList, 0)
duplicates := make(map[string]bool, 0)
- for _, mediaThumbnail := range r.MediaThumbnails {
+ for _, mediaThumbnail := range r.AllMediaThumbnails() {
if _, found := duplicates[mediaThumbnail.URL]; !found {
duplicates[mediaThumbnail.URL] = true
enclosures = append(enclosures, &model.Enclosure{
URL: mediaThumbnail.URL,
- MimeType: "image/*",
- Size: 0,
+ MimeType: mediaThumbnail.MimeType(),
+ Size: mediaThumbnail.Size(),
})
}
}
@@ -285,13 +244,7 @@ func (r *rssItem) Enclosures() model.EnclosureList {
}
}
- for _, mediaContentItem := range r.MediaGroup {
- for _, mediaContent := range mediaContentItem.MediaList {
- r.MediaContents = append(r.MediaContents, mediaContent)
- }
- }
-
- for _, mediaContent := range r.MediaContents {
+ for _, mediaContent := range r.AllMediaContents() {
if _, found := duplicates[mediaContent.URL]; !found {
duplicates[mediaContent.URL] = true
enclosures = append(enclosures, &model.Enclosure{
@@ -302,6 +255,17 @@ func (r *rssItem) Enclosures() model.EnclosureList {
}
}
+ for _, mediaPeerLink := range r.AllMediaPeerLinks() {
+ if _, found := duplicates[mediaPeerLink.URL]; !found {
+ duplicates[mediaPeerLink.URL] = true
+ enclosures = append(enclosures, &model.Enclosure{
+ URL: mediaPeerLink.URL,
+ MimeType: mediaPeerLink.MimeType(),
+ Size: mediaPeerLink.Size(),
+ })
+ }
+ }
+
return enclosures
}