adapter.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package rdf // import "miniflux.app/v2/internal/reader/rdf"
  4. import (
  5. "html"
  6. "log/slog"
  7. "strings"
  8. "time"
  9. "miniflux.app/v2/internal/crypto"
  10. "miniflux.app/v2/internal/model"
  11. "miniflux.app/v2/internal/reader/date"
  12. "miniflux.app/v2/internal/reader/sanitizer"
  13. "miniflux.app/v2/internal/urllib"
  14. )
  15. type RDFAdapter struct {
  16. rdf *RDF
  17. }
  18. func NewRDFAdapter(rdf *RDF) *RDFAdapter {
  19. return &RDFAdapter{rdf}
  20. }
  21. func (r *RDFAdapter) BuildFeed(baseURL string) *model.Feed {
  22. feed := &model.Feed{
  23. Title: stripTags(r.rdf.Channel.Title),
  24. FeedURL: strings.TrimSpace(baseURL),
  25. SiteURL: strings.TrimSpace(r.rdf.Channel.Link),
  26. }
  27. if feed.Title == "" {
  28. feed.Title = baseURL
  29. }
  30. if siteURL, err := urllib.AbsoluteURL(feed.FeedURL, feed.SiteURL); err == nil {
  31. feed.SiteURL = siteURL
  32. }
  33. for _, item := range r.rdf.Items {
  34. entry := model.NewEntry()
  35. itemLink := strings.TrimSpace(item.Link)
  36. // Populate the entry URL.
  37. if itemLink == "" {
  38. entry.URL = feed.SiteURL // Fallback to the feed URL if the entry URL is empty.
  39. } else if entryURL, err := urllib.AbsoluteURL(feed.SiteURL, itemLink); err == nil {
  40. entry.URL = entryURL
  41. } else {
  42. entry.URL = itemLink
  43. }
  44. // Populate the entry title.
  45. for _, title := range []string{item.Title, item.DublinCoreTitle} {
  46. title = strings.TrimSpace(title)
  47. if title != "" {
  48. entry.Title = html.UnescapeString(title)
  49. break
  50. }
  51. }
  52. // If the entry title is empty, we use the entry URL as a fallback.
  53. if entry.Title == "" {
  54. entry.Title = entry.URL
  55. }
  56. // Populate the entry content.
  57. if item.DublinCoreContent != "" {
  58. entry.Content = item.DublinCoreContent
  59. } else {
  60. entry.Content = item.Description
  61. }
  62. // Generate the entry hash.
  63. hashValue := itemLink
  64. if hashValue == "" {
  65. hashValue = item.Title + item.Description // Fallback to the title and description if the link is empty.
  66. }
  67. entry.Hash = crypto.Hash(hashValue)
  68. // Populate the entry date.
  69. entry.Date = time.Now()
  70. if item.DublinCoreDate != "" {
  71. if itemDate, err := date.Parse(item.DublinCoreDate); err != nil {
  72. slog.Debug("Unable to parse date from RDF feed",
  73. slog.String("date", item.DublinCoreDate),
  74. slog.String("link", itemLink),
  75. slog.Any("error", err),
  76. )
  77. } else {
  78. entry.Date = itemDate
  79. }
  80. }
  81. // Populate the entry author.
  82. switch {
  83. case item.DublinCoreCreator != "":
  84. entry.Author = stripTags(item.DublinCoreCreator)
  85. case r.rdf.Channel.DublinCoreCreator != "":
  86. entry.Author = stripTags(r.rdf.Channel.DublinCoreCreator)
  87. }
  88. feed.Entries = append(feed.Entries, entry)
  89. }
  90. return feed
  91. }
  92. func stripTags(value string) string {
  93. return strings.TrimSpace(sanitizer.StripTags(value))
  94. }