adapter.go 2.8 KB

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