adapter.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  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. Description: strings.TrimSpace(r.rdf.Channel.Description),
  27. }
  28. if feed.Title == "" {
  29. feed.Title = baseURL
  30. }
  31. if siteURL, err := urllib.AbsoluteURL(feed.FeedURL, feed.SiteURL); err == nil {
  32. feed.SiteURL = siteURL
  33. }
  34. for _, item := range r.rdf.Items {
  35. entry := model.NewEntry()
  36. itemLink := strings.TrimSpace(item.Link)
  37. // Populate the entry URL.
  38. if itemLink == "" {
  39. entry.URL = feed.SiteURL // Fallback to the feed URL if the entry URL is empty.
  40. } else if entryURL, err := urllib.AbsoluteURL(feed.SiteURL, itemLink); err == nil {
  41. entry.URL = entryURL
  42. } else {
  43. entry.URL = itemLink
  44. }
  45. // Populate the entry title.
  46. for _, title := range []string{item.Title, item.DublinCoreTitle} {
  47. title = strings.TrimSpace(title)
  48. if title != "" {
  49. entry.Title = html.UnescapeString(title)
  50. break
  51. }
  52. }
  53. // If the entry title is empty, we use the entry URL as a fallback.
  54. if entry.Title == "" {
  55. entry.Title = entry.URL
  56. }
  57. // Populate the entry content.
  58. if item.DublinCoreContent != "" {
  59. entry.Content = item.DublinCoreContent
  60. } else {
  61. entry.Content = item.Description
  62. }
  63. // Generate the entry hash.
  64. hashValue := itemLink
  65. if hashValue == "" {
  66. hashValue = item.Title + item.Description // Fallback to the title and description if the link is empty.
  67. }
  68. entry.Hash = crypto.SHA256(hashValue)
  69. // Populate the entry date.
  70. entry.Date = time.Now()
  71. if item.DublinCoreDate != "" {
  72. if itemDate, err := date.Parse(item.DublinCoreDate); err != nil {
  73. slog.Debug("Unable to parse date from RDF feed",
  74. slog.String("date", item.DublinCoreDate),
  75. slog.String("link", itemLink),
  76. slog.Any("error", err),
  77. )
  78. } else {
  79. entry.Date = itemDate
  80. }
  81. }
  82. // Populate the entry author.
  83. switch {
  84. case item.DublinCoreCreator != "":
  85. entry.Author = stripTags(item.DublinCoreCreator)
  86. case r.rdf.Channel.DublinCoreCreator != "":
  87. entry.Author = stripTags(r.rdf.Channel.DublinCoreCreator)
  88. }
  89. feed.Entries = append(feed.Entries, entry)
  90. }
  91. return feed
  92. }
  93. func stripTags(value string) string {
  94. return strings.TrimSpace(sanitizer.StripTags(value))
  95. }