Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions internal/reader/atom/atom_03_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package atom // import "miniflux.app/v2/internal/reader/atom"

import (
"log/slog"
"strings"
"time"

"miniflux.app/v2/internal/crypto"
Expand All @@ -19,16 +20,17 @@ type atom03Adapter struct {
}

func (a *atom03Adapter) buildFeed(baseURL string) *model.Feed {
feed := new(model.Feed)
feed := &model.Feed{
FeedURL: baseURL,
SiteURL: baseURL,
}

// Populate the feed URL.
feedURL := a.atomFeed.Links.firstLinkWithRelation("self")
if feedURL != "" {
if absoluteFeedURL, err := urllib.ResolveToAbsoluteURL(baseURL, feedURL); err == nil {
feed.FeedURL = absoluteFeedURL
}
} else {
feed.FeedURL = baseURL
}

// Populate the site URL.
Expand All @@ -37,8 +39,6 @@ func (a *atom03Adapter) buildFeed(baseURL string) *model.Feed {
if absoluteSiteURL, err := urllib.ResolveToAbsoluteURL(baseURL, siteURL); err == nil {
feed.SiteURL = absoluteSiteURL
}
} else {
feed.SiteURL = baseURL
}

// Populate the feed title.
Expand Down Expand Up @@ -69,6 +69,7 @@ func (a *atom03Adapter) buildFeed(baseURL string) *model.Feed {
if entry.Title == "" {
entry.Title = sanitizer.TruncateHTML(entry.Content, 100)
}

if entry.Title == "" {
entry.Title = entry.URL
}
Expand All @@ -81,17 +82,24 @@ func (a *atom03Adapter) buildFeed(baseURL string) *model.Feed {

// Populate the entry date.
for _, value := range []string{atomEntry.Issued, atomEntry.Modified, atomEntry.Created} {
if parsedDate, err := date.Parse(value); err == nil {
entry.Date = parsedDate
break
} else {
if value = strings.TrimSpace(value); value == "" {
continue
}

parsedDate, err := date.Parse(value)
if err != nil {
slog.Debug("Unable to parse date from Atom 0.3 feed",
slog.String("date", value),
slog.String("id", atomEntry.ID),
slog.Any("error", err),
)
continue
}

entry.Date = parsedDate
break
}

if entry.Date.IsZero() {
entry.Date = time.Now()
}
Expand Down
183 changes: 100 additions & 83 deletions internal/reader/atom/atom_10_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ package atom // import "miniflux.app/v2/internal/reader/atom"

import (
"log/slog"
"slices"
"sort"
"strconv"
"strings"
"time"
Expand All @@ -22,21 +20,18 @@ type atom10Adapter struct {
atomFeed *atom10Feed
}

func NewAtom10Adapter(atomFeed *atom10Feed) *atom10Adapter {
return &atom10Adapter{atomFeed}
}

func (a *atom10Adapter) BuildFeed(baseURL string) *model.Feed {
feed := new(model.Feed)
func (a *atom10Adapter) buildFeed(baseURL string) *model.Feed {
feed := &model.Feed{
FeedURL: baseURL,
SiteURL: baseURL,
}

// Populate the feed URL.
feedURL := a.atomFeed.Links.firstLinkWithRelation("self")
if feedURL != "" {
if absoluteFeedURL, err := urllib.ResolveToAbsoluteURL(baseURL, feedURL); err == nil {
feed.FeedURL = absoluteFeedURL
}
} else {
feed.FeedURL = baseURL
}

// Populate the site URL.
Expand All @@ -45,8 +40,6 @@ func (a *atom10Adapter) BuildFeed(baseURL string) *model.Feed {
if absoluteSiteURL, err := urllib.ResolveToAbsoluteURL(baseURL, siteURL); err == nil {
feed.SiteURL = absoluteSiteURL
}
} else {
feed.SiteURL = baseURL
}

// Populate the feed title.
Expand All @@ -59,15 +52,17 @@ func (a *atom10Adapter) BuildFeed(baseURL string) *model.Feed {
feed.Description = a.atomFeed.Subtitle.body()

// Populate the feed icon.
if a.atomFeed.Icon != "" {
if absoluteIconURL, err := urllib.ResolveToAbsoluteURL(feed.SiteURL, a.atomFeed.Icon); err == nil {
feed.IconURL = absoluteIconURL
for _, value := range []string{a.atomFeed.Icon, a.atomFeed.Logo} {
if value = strings.TrimSpace(value); value == "" {
continue
}
} else if a.atomFeed.Logo != "" {
if absoluteLogoURL, err := urllib.ResolveToAbsoluteURL(feed.SiteURL, a.atomFeed.Logo); err == nil {
feed.IconURL = absoluteLogoURL

if iconURL, err := urllib.ResolveToAbsoluteURL(feed.SiteURL, value); err == nil {
feed.IconURL = iconURL
break
}
}

feed.Entries = a.populateEntries(feed.SiteURL)
return feed
}
Expand Down Expand Up @@ -109,39 +104,39 @@ func (a *atom10Adapter) populateEntries(siteURL string) model.Entries {
if len(authors) == 0 {
authors = a.atomFeed.Authors.personNames()
}
sort.Strings(authors)
authors = slices.Compact(authors)

entry.Author = strings.Join(authors, ", ")

// Populate the entry date.
for _, value := range []string{atomEntry.Published, atomEntry.Updated} {
if value != "" {
if parsedDate, err := date.Parse(value); err != nil {
slog.Debug("Unable to parse date from Atom 1.0 feed",
slog.String("date", value),
slog.String("url", entry.URL),
slog.Any("error", err),
)
} else {
entry.Date = parsedDate
break
}
if value = strings.TrimSpace(value); value == "" {
continue
}

parsedDate, err := date.Parse(value)
Comment thread
gudvinr marked this conversation as resolved.
if err != nil {
slog.Debug("Unable to parse date from Atom 1.0 feed",
slog.String("date", value),
slog.String("url", entry.URL),
slog.Any("error", err),
)
continue
}

entry.Date = parsedDate
break
}

if entry.Date.IsZero() {
entry.Date = time.Now()
}

// Populate categories.
categories := atomEntry.Categories.CategoryNames()
if len(categories) == 0 {
categories = a.atomFeed.Categories.CategoryNames()
entry.Tags = atomEntry.Categories.CategoryNames()
if len(entry.Tags) == 0 {
entry.Tags = a.atomFeed.Categories.CategoryNames()
}

// Sort and deduplicate categories.
sort.Strings(categories)
entry.Tags = slices.Compact(categories)

// Populate the commentsURL if defined.
// See https://tools.ietf.org/html/rfc4685#section-4
// If the type attribute of the atom:link is omitted, its value is assumed to be "application/atom+xml".
Expand All @@ -167,22 +162,28 @@ func (a *atom10Adapter) populateEntries(siteURL string) model.Entries {
if mediaURL == "" {
continue
}
if _, found := uniqueEnclosuresMap[mediaURL]; !found {
if mediaAbsoluteURL, err := urllib.ResolveToAbsoluteURL(siteURL, mediaURL); err != nil {
slog.Debug("Unable to build absolute URL for media thumbnail",
slog.String("url", mediaThumbnail.URL),
slog.String("site_url", siteURL),
slog.Any("error", err),
)
} else {
uniqueEnclosuresMap[mediaAbsoluteURL] = true
entry.Enclosures = append(entry.Enclosures, &model.Enclosure{
URL: mediaAbsoluteURL,
MimeType: mediaThumbnail.MimeType(),
Size: mediaThumbnail.Size(),
})
}

if _, found := uniqueEnclosuresMap[mediaURL]; found {
continue
}

mediaAbsoluteURL, err := urllib.ResolveToAbsoluteURL(siteURL, mediaURL)
if err != nil {
slog.Debug("Unable to build absolute URL for media thumbnail",
slog.String("url", mediaThumbnail.URL),
slog.String("site_url", siteURL),
slog.Any("error", err),
)
continue
}

uniqueEnclosuresMap[mediaAbsoluteURL] = true

entry.Enclosures = append(entry.Enclosures, &model.Enclosure{
URL: mediaAbsoluteURL,
MimeType: mediaThumbnail.MimeType(),
Size: mediaThumbnail.Size(),
})
}

for _, link := range atomEntry.Links.findAllLinksWithRelation("enclosure") {
Expand All @@ -193,63 +194,79 @@ func (a *atom10Adapter) populateEntries(siteURL string) model.Entries {
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
} else {
if _, found := uniqueEnclosuresMap[absoluteEnclosureURL]; !found {
uniqueEnclosuresMap[absoluteEnclosureURL] = true
length, _ := strconv.ParseInt(link.Length, 10, 0)
entry.Enclosures = append(entry.Enclosures, &model.Enclosure{
URL: absoluteEnclosureURL,
MimeType: link.Type,
Size: length,
})
}
continue
}

if _, found := uniqueEnclosuresMap[absoluteEnclosureURL]; found {
continue
}

uniqueEnclosuresMap[absoluteEnclosureURL] = true

length, _ := strconv.ParseInt(link.Length, 10, 0)
entry.Enclosures = append(entry.Enclosures, &model.Enclosure{
URL: absoluteEnclosureURL,
MimeType: link.Type,
Size: length,
})
}

for _, mediaContent := range atomEntry.AllMediaContents() {
mediaURL := strings.TrimSpace(mediaContent.URL)
if mediaURL == "" {
continue
}
if mediaAbsoluteURL, err := urllib.ResolveToAbsoluteURL(siteURL, mediaURL); err != nil {

mediaAbsoluteURL, err := urllib.ResolveToAbsoluteURL(siteURL, mediaURL)
if err != nil {
slog.Debug("Unable to build absolute URL for media content",
slog.String("url", mediaContent.URL),
slog.String("site_url", siteURL),
slog.Any("error", err),
)
} else {
if _, found := uniqueEnclosuresMap[mediaAbsoluteURL]; !found {
uniqueEnclosuresMap[mediaAbsoluteURL] = true
entry.Enclosures = append(entry.Enclosures, &model.Enclosure{
URL: mediaAbsoluteURL,
MimeType: mediaContent.MimeType(),
Size: mediaContent.Size(),
})
}
continue
}

if _, found := uniqueEnclosuresMap[mediaAbsoluteURL]; found {
continue
}

uniqueEnclosuresMap[mediaAbsoluteURL] = true

entry.Enclosures = append(entry.Enclosures, &model.Enclosure{
URL: mediaAbsoluteURL,
MimeType: mediaContent.MimeType(),
Size: mediaContent.Size(),
})
}

for _, mediaPeerLink := range atomEntry.AllMediaPeerLinks() {
mediaURL := strings.TrimSpace(mediaPeerLink.URL)
if mediaURL == "" {
continue
}
if mediaAbsoluteURL, err := urllib.ResolveToAbsoluteURL(siteURL, mediaURL); err != nil {

mediaAbsoluteURL, err := urllib.ResolveToAbsoluteURL(siteURL, mediaURL)
if err != nil {
slog.Debug("Unable to build absolute URL for media peer link",
slog.String("url", mediaPeerLink.URL),
slog.String("site_url", siteURL),
slog.Any("error", err),
)
} else {
if _, found := uniqueEnclosuresMap[mediaAbsoluteURL]; !found {
uniqueEnclosuresMap[mediaAbsoluteURL] = true
entry.Enclosures = append(entry.Enclosures, &model.Enclosure{
URL: mediaAbsoluteURL,
MimeType: mediaPeerLink.MimeType(),
Size: mediaPeerLink.Size(),
})
}
continue
}

if _, found := uniqueEnclosuresMap[mediaAbsoluteURL]; found {
continue
}

uniqueEnclosuresMap[mediaAbsoluteURL] = true

entry.Enclosures = append(entry.Enclosures, &model.Enclosure{
URL: mediaAbsoluteURL,
MimeType: mediaPeerLink.MimeType(),
Size: mediaPeerLink.Size(),
})
}

entries = append(entries, entry)
Expand Down
Loading