mirror of
https://github.com/miniflux/v2.git
synced 2025-08-26 18:21:01 +00:00
Add support of media elements for Atom feeds
This commit is contained in:
parent
f90e9dfab0
commit
912a98788e
6 changed files with 569 additions and 102 deletions
176
reader/media/media.go
Normal file
176
reader/media/media.go
Normal file
|
@ -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 ""
|
||||
}
|
110
reader/media/media_test.go
Normal file
110
reader/media/media_test.go
Normal file
|
@ -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`)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue