atom_03_adapter.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package atom // import "miniflux.app/v2/internal/reader/atom"
  4. import (
  5. "log/slog"
  6. "strings"
  7. "time"
  8. "miniflux.app/v2/internal/crypto"
  9. "miniflux.app/v2/internal/model"
  10. "miniflux.app/v2/internal/reader/date"
  11. "miniflux.app/v2/internal/reader/language"
  12. "miniflux.app/v2/internal/reader/sanitizer"
  13. "miniflux.app/v2/internal/urllib"
  14. )
  15. type atom03Adapter struct {
  16. atomFeed *atom03Feed
  17. }
  18. func (a *atom03Adapter) buildFeed(baseURL string) *model.Feed {
  19. feed := &model.Feed{
  20. FeedURL: baseURL,
  21. SiteURL: baseURL,
  22. }
  23. // Populate the feed URL.
  24. feedURL := a.atomFeed.Links.firstLinkWithRelation("self")
  25. if feedURL != "" {
  26. if absoluteFeedURL, err := urllib.ResolveToAbsoluteURL(baseURL, feedURL); err == nil {
  27. feed.FeedURL = absoluteFeedURL
  28. }
  29. }
  30. // Populate the site URL.
  31. siteURL := a.atomFeed.Links.originalLink()
  32. if siteURL != "" {
  33. if absoluteSiteURL, err := urllib.ResolveToAbsoluteURL(baseURL, siteURL); err == nil {
  34. feed.SiteURL = absoluteSiteURL
  35. }
  36. }
  37. // Populate the feed title.
  38. feed.Title = a.atomFeed.Title.content()
  39. if feed.Title == "" {
  40. feed.Title = feed.SiteURL
  41. }
  42. feed.Language = language.Normalize(a.atomFeed.Language)
  43. for _, atomEntry := range a.atomFeed.Entries {
  44. entry := model.NewEntry()
  45. // Populate the entry language. xml:lang applies to the whole
  46. // subtree it is declared on, so an entry without its own
  47. // xml:lang inherits the feed-level value.
  48. entry.Language = language.Normalize(atomEntry.Language)
  49. if entry.Language == "" {
  50. entry.Language = language.Normalize(a.atomFeed.Language)
  51. }
  52. // Populate the entry URL.
  53. entry.URL = atomEntry.Links.originalLink()
  54. if entry.URL != "" {
  55. if absoluteEntryURL, err := urllib.ResolveToAbsoluteURL(feed.SiteURL, entry.URL); err == nil {
  56. entry.URL = absoluteEntryURL
  57. }
  58. }
  59. // Populate the entry content.
  60. entry.Content = atomEntry.Content.content()
  61. if entry.Content == "" {
  62. entry.Content = atomEntry.Summary.content()
  63. }
  64. // Populate the entry title.
  65. entry.Title = atomEntry.Title.content()
  66. if entry.Title == "" {
  67. entry.Title = sanitizer.TruncateHTML(entry.Content, 100)
  68. }
  69. if entry.Title == "" {
  70. entry.Title = entry.URL
  71. }
  72. // Populate the entry author.
  73. entry.Author = atomEntry.Author.PersonName()
  74. if entry.Author == "" {
  75. entry.Author = a.atomFeed.Author.PersonName()
  76. }
  77. // Populate the entry date.
  78. for _, value := range []string{atomEntry.Issued, atomEntry.Modified, atomEntry.Created} {
  79. if value = strings.TrimSpace(value); value == "" {
  80. continue
  81. }
  82. parsedDate, err := date.Parse(value)
  83. if err != nil {
  84. slog.Debug("Unable to parse date from Atom 0.3 feed",
  85. slog.String("date", value),
  86. slog.String("id", atomEntry.ID),
  87. slog.Any("error", err),
  88. )
  89. continue
  90. }
  91. entry.Date = parsedDate
  92. break
  93. }
  94. if entry.Date.IsZero() {
  95. entry.Date = time.Now()
  96. }
  97. // Generate the entry hash.
  98. for _, value := range []string{atomEntry.ID, atomEntry.Links.originalLink()} {
  99. if value != "" {
  100. entry.Hash = crypto.SHA256(value)
  101. break
  102. }
  103. }
  104. feed.Entries = append(feed.Entries, entry)
  105. }
  106. return feed
  107. }