diff --git a/internal/reader/googleplay/googleplay.go b/internal/reader/googleplay/googleplay.go new file mode 100644 index 00000000..38dcc71f --- /dev/null +++ b/internal/reader/googleplay/googleplay.go @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package googleplay // import "miniflux.app/v2/internal/reader/googleplay" + +// Specs: +// https://support.google.com/googleplay/podcasts/answer/6260341 +// https://www.google.com/schemas/play-podcasts/1.0/play-podcasts.xsd +type GooglePlayFeedElement struct { + GooglePlayAuthor string `xml:"http://www.google.com/schemas/play-podcasts/1.0 author"` + GooglePlayEmail string `xml:"http://www.google.com/schemas/play-podcasts/1.0 email"` + GooglePlayImage GooglePlayImageElement `xml:"http://www.google.com/schemas/play-podcasts/1.0 image"` + GooglePlayDescription string `xml:"http://www.google.com/schemas/play-podcasts/1.0 description"` + GooglePlayCategory GooglePlayCategoryElement `xml:"http://www.google.com/schemas/play-podcasts/1.0 category"` +} + +type GooglePlayItemElement struct { + GooglePlayAuthor string `xml:"http://www.google.com/schemas/play-podcasts/1.0 author"` + GooglePlayDescription string `xml:"http://www.google.com/schemas/play-podcasts/1.0 description"` + GooglePlayExplicit string `xml:"http://www.google.com/schemas/play-podcasts/1.0 explicit"` + GooglePlayBlock string `xml:"http://www.google.com/schemas/play-podcasts/1.0 block"` + GooglePlayNewFeedURL string `xml:"http://www.google.com/schemas/play-podcasts/1.0 new-feed-url"` +} + +type GooglePlayImageElement struct { + Href string `xml:"href,attr"` +} + +type GooglePlayCategoryElement struct { + Text string `xml:"text,attr"` +} diff --git a/internal/reader/itunes/itunes.go b/internal/reader/itunes/itunes.go new file mode 100644 index 00000000..0382493f --- /dev/null +++ b/internal/reader/itunes/itunes.go @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package itunes // import "miniflux.app/v2/internal/reader/itunes" + +import "strings" + +// Specs: https://help.apple.com/itc/podcasts_connect/#/itcb54353390 +type ItunesFeedElement struct { + ItunesAuthor string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd author"` + ItunesBlock string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd block"` + ItunesCategories []ItunesCategoryElement `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd category"` + ItunesComplete string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd complete"` + ItunesCopyright string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd copyright"` + ItunesExplicit string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd explicit"` + ItunesImage ItunesImageElement `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd image"` + Keywords string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd keywords"` + ItunesNewFeedURL string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd new-feed-url"` + ItunesOwner ItunesOwnerElement `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd owner"` + ItunesSummary string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd summary"` + ItunesTitle string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd title"` + ItunesType string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd type"` +} + +type ItunesItemElement struct { + ItunesAuthor string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd author"` + ItunesEpisode string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd episode"` + ItunesEpisodeType string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd episodeType"` + ItunesExplicit string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd explicit"` + ItunesDuration string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd duration"` + ItunesImage ItunesImageElement `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd image"` + ItunesSeason string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd season"` + ItunesSubtitle string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd subtitle"` + ItunesSummary string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd summary"` + ItunesTitle string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd title"` + ItunesTranscript string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd transcript"` +} + +type ItunesImageElement struct { + Href string `xml:"href,attr"` +} + +type ItunesCategoryElement struct { + Text string `xml:"text,attr"` + SubCategory *ItunesCategoryElement `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd category"` +} + +type ItunesOwnerElement struct { + Name string `xml:"name"` + Email string `xml:"email"` +} + +func (i *ItunesOwnerElement) String() string { + var name string + + switch { + case i.Name != "": + name = i.Name + case i.Email != "": + name = i.Email + } + + return strings.TrimSpace(name) +} diff --git a/internal/reader/rss/podcast.go b/internal/reader/rss/podcast.go index 867bc03b..9a1f365b 100644 --- a/internal/reader/rss/podcast.go +++ b/internal/reader/rss/podcast.go @@ -12,84 +12,6 @@ import ( var ErrInvalidDurationFormat = errors.New("rss: invalid duration format") -// PodcastFeedElement represents iTunes and GooglePlay feed XML elements. -// Specs: -// - https://github.com/simplepie/simplepie-ng/wiki/Spec:-iTunes-Podcast-RSS -// - https://support.google.com/podcast-publishers/answer/9889544 -type PodcastFeedElement struct { - ItunesAuthor string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd author"` - Subtitle string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd subtitle"` - Summary string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd summary"` - PodcastOwner PodcastOwner `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd owner"` - GooglePlayAuthor string `xml:"http://www.google.com/schemas/play-podcasts/1.0 author"` -} - -// PodcastEntryElement represents iTunes and GooglePlay entry XML elements. -type PodcastEntryElement struct { - ItunesAuthor string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd author"` - Subtitle string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd subtitle"` - Summary string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd summary"` - Duration string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd duration"` - PodcastOwner PodcastOwner `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd owner"` - GooglePlayAuthor string `xml:"http://www.google.com/schemas/play-podcasts/1.0 author"` - GooglePlayDescription string `xml:"http://www.google.com/schemas/play-podcasts/1.0 description"` -} - -// PodcastOwner represents contact information for the podcast owner. -type PodcastOwner struct { - Name string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd name"` - Email string `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd email"` -} - -func (p *PodcastOwner) String() string { - var name string - - switch { - case p.Name != "": - name = p.Name - case p.Email != "": - name = p.Email - } - - return strings.TrimSpace(name) -} - -// Image represents podcast artwork. -type Image struct { - URL string `xml:"href,attr"` -} - -// PodcastAuthor returns the author of the podcast. -func (e *PodcastFeedElement) PodcastAuthor() string { - author := "" - - switch { - case e.ItunesAuthor != "": - author = e.ItunesAuthor - case e.GooglePlayAuthor != "": - author = e.GooglePlayAuthor - case e.PodcastOwner.String() != "": - author = e.PodcastOwner.String() - } - - return strings.TrimSpace(author) -} - -// PodcastDescription returns the description of the podcast. -func (e *PodcastEntryElement) PodcastDescription() string { - description := "" - - switch { - case e.GooglePlayDescription != "": - description = e.GooglePlayDescription - case e.Summary != "": - description = e.Summary - case e.Subtitle != "": - description = e.Subtitle - } - return strings.TrimSpace(description) -} - // normalizeDuration returns the duration tag value as a number of minutes func normalizeDuration(rawDuration string) (int, error) { var sumSeconds int diff --git a/internal/reader/rss/rss.go b/internal/reader/rss/rss.go index cb769141..cd1442bd 100644 --- a/internal/reader/rss/rss.go +++ b/internal/reader/rss/rss.go @@ -16,6 +16,8 @@ import ( "miniflux.app/v2/internal/model" "miniflux.app/v2/internal/reader/date" "miniflux.app/v2/internal/reader/dublincore" + "miniflux.app/v2/internal/reader/googleplay" + "miniflux.app/v2/internal/reader/itunes" "miniflux.app/v2/internal/reader/media" "miniflux.app/v2/internal/reader/sanitizer" "miniflux.app/v2/internal/urllib" @@ -40,7 +42,8 @@ type rssChannel struct { TimeToLive rssTTL `xml:"rss ttl"` Items []rssItem `xml:"rss item"` AtomLinks - PodcastFeedElement + itunes.ItunesFeedElement + googleplay.GooglePlayFeedElement } type rssTTL struct { @@ -128,16 +131,18 @@ func (r *rssFeed) feedURL() string { } func (r rssFeed) feedAuthor() string { - author := r.Channel.PodcastAuthor() + var author string switch { + case r.Channel.ItunesAuthor != "": + author = r.Channel.ItunesAuthor + case r.Channel.GooglePlayAuthor != "": + author = r.Channel.GooglePlayAuthor + case r.Channel.ItunesOwner.String() != "": + author = r.Channel.ItunesOwner.String() case r.Channel.ManagingEditor != "": author = r.Channel.ManagingEditor case r.Channel.Webmaster != "": author = r.Channel.Webmaster - case r.Channel.GooglePlayAuthor != "": - author = r.Channel.GooglePlayAuthor - case r.Channel.PodcastOwner.String() != "": - author = r.Channel.PodcastOwner.String() } return sanitizer.StripTags(strings.TrimSpace(author)) } @@ -186,10 +191,11 @@ type rssItem struct { Categories []rssCategory `xml:"rss category"` dublincore.DublinCoreItemElement FeedBurnerElement - PodcastEntryElement media.Element AtomAuthor AtomLinks + itunes.ItunesItemElement + googleplay.GooglePlayItemElement } func (r *rssItem) Transform() *model.Entry { @@ -203,7 +209,7 @@ func (r *rssItem) Transform() *model.Entry { entry.Title = r.entryTitle() entry.Enclosures = r.entryEnclosures() entry.Tags = r.entryCategories() - if duration, err := normalizeDuration(r.Duration); err == nil { + if duration, err := normalizeDuration(r.ItunesDuration); err == nil { entry.ReadingTime = duration } @@ -237,8 +243,6 @@ func (r *rssItem) entryAuthor() string { var author string switch { - case r.PodcastOwner.String() != "": - author = r.PodcastOwner.String() case r.GooglePlayAuthor != "": author = r.GooglePlayAuthor case r.ItunesAuthor != "": @@ -277,7 +281,13 @@ func (r *rssItem) entryTitle() string { } func (r *rssItem) entryContent() string { - for _, value := range []string{r.DublinCoreContent, r.Description, r.PodcastDescription()} { + for _, value := range []string{ + r.DublinCoreContent, + r.Description, + r.GooglePlayDescription, + r.ItunesSummary, + r.ItunesSubtitle, + } { if value != "" { return value }