handler.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package opml // import "miniflux.app/v2/internal/reader/opml"
  4. import (
  5. "fmt"
  6. "io"
  7. "miniflux.app/v2/internal/model"
  8. "miniflux.app/v2/internal/storage"
  9. "miniflux.app/v2/internal/validator"
  10. )
  11. // Handler handles the logic for OPML import/export.
  12. type Handler struct {
  13. store *storage.Storage
  14. }
  15. // Export exports user feeds to OPML.
  16. func (h *Handler) Export(userID int64) (string, error) {
  17. feeds, err := h.store.Feeds(userID)
  18. if err != nil {
  19. return "", err
  20. }
  21. subscriptions := make([]subcription, 0, len(feeds))
  22. for _, feed := range feeds {
  23. subscriptions = append(subscriptions, subcription{
  24. Title: feed.Title,
  25. FeedURL: feed.FeedURL,
  26. SiteURL: feed.SiteURL,
  27. Description: feed.Description,
  28. CategoryName: feed.Category.Title,
  29. ScraperRules: feed.ScraperRules,
  30. RewriteRules: feed.RewriteRules,
  31. UrlRewriteRules: feed.UrlRewriteRules,
  32. BlocklistRules: feed.BlocklistRules,
  33. KeeplistRules: feed.KeeplistRules,
  34. BlockFilterEntryRules: feed.BlockFilterEntryRules,
  35. KeepFilterEntryRules: feed.KeepFilterEntryRules,
  36. UserAgent: feed.UserAgent,
  37. Crawler: feed.Crawler,
  38. IgnoreHTTPCache: feed.IgnoreHTTPCache,
  39. FetchViaProxy: feed.FetchViaProxy,
  40. Disabled: feed.Disabled,
  41. NoMediaPlayer: feed.NoMediaPlayer,
  42. HideGlobally: feed.HideGlobally,
  43. AllowSelfSignedCertificates: feed.AllowSelfSignedCertificates,
  44. DisableHTTP2: feed.DisableHTTP2,
  45. IgnoreEntryUpdates: feed.IgnoreEntryUpdates,
  46. })
  47. }
  48. return serialize(subscriptions), nil
  49. }
  50. // Import parses and create feeds from an OPML import.
  51. func (h *Handler) Import(userID int64, data io.Reader) error {
  52. subscriptions, err := parse(data)
  53. if err != nil {
  54. return err
  55. }
  56. for _, subscription := range subscriptions {
  57. if h.store.FeedURLExists(userID, subscription.FeedURL) {
  58. continue
  59. }
  60. category, err := h.resolveCategory(userID, subscription.CategoryName)
  61. if err != nil {
  62. return err
  63. }
  64. if validationErr := validateSubscription(userID, category.ID, h.store, subscription); validationErr != nil {
  65. return fmt.Errorf(`opml: invalid feed settings for %q: %w`, subscription.FeedURL, validationErr)
  66. }
  67. feed := &model.Feed{
  68. UserID: userID,
  69. Title: subscription.Title,
  70. FeedURL: subscription.FeedURL,
  71. SiteURL: subscription.SiteURL,
  72. Description: subscription.Description,
  73. Category: category,
  74. }
  75. applySubscriptionSettings(feed, subscription)
  76. if err := h.store.CreateFeed(feed); err != nil {
  77. return fmt.Errorf(`opml: unable to create this feed: %q`, subscription.FeedURL)
  78. }
  79. }
  80. return nil
  81. }
  82. func (h *Handler) resolveCategory(userID int64, categoryName string) (*model.Category, error) {
  83. if categoryName == "" {
  84. category, err := h.store.FirstCategory(userID)
  85. if err != nil {
  86. return nil, fmt.Errorf("opml: unable to find first category: %w", err)
  87. }
  88. return category, nil
  89. }
  90. category, err := h.store.CategoryByTitle(userID, categoryName)
  91. if err != nil {
  92. return nil, fmt.Errorf("opml: unable to search category by title: %w", err)
  93. }
  94. if category == nil {
  95. category, err = h.store.CreateCategory(userID, &model.CategoryCreationRequest{Title: categoryName})
  96. if err != nil {
  97. return nil, fmt.Errorf(`opml: unable to create this category: %q`, categoryName)
  98. }
  99. }
  100. return category, nil
  101. }
  102. func applySubscriptionSettings(feed *model.Feed, s subcription) {
  103. feed.ScraperRules = s.ScraperRules
  104. feed.RewriteRules = s.RewriteRules
  105. feed.UrlRewriteRules = s.UrlRewriteRules
  106. feed.BlocklistRules = s.BlocklistRules
  107. feed.KeeplistRules = s.KeeplistRules
  108. feed.BlockFilterEntryRules = s.BlockFilterEntryRules
  109. feed.KeepFilterEntryRules = s.KeepFilterEntryRules
  110. feed.UserAgent = s.UserAgent
  111. feed.Crawler = s.Crawler
  112. feed.IgnoreHTTPCache = s.IgnoreHTTPCache
  113. feed.FetchViaProxy = s.FetchViaProxy
  114. feed.Disabled = s.Disabled
  115. feed.NoMediaPlayer = s.NoMediaPlayer
  116. feed.HideGlobally = s.HideGlobally
  117. feed.AllowSelfSignedCertificates = s.AllowSelfSignedCertificates
  118. feed.DisableHTTP2 = s.DisableHTTP2
  119. feed.IgnoreEntryUpdates = s.IgnoreEntryUpdates
  120. }
  121. func validateSubscription(userID, categoryID int64, store *storage.Storage, s subcription) error {
  122. feedCreationRequest := &model.FeedCreationRequest{
  123. FeedURL: s.FeedURL,
  124. CategoryID: categoryID,
  125. UserAgent: s.UserAgent,
  126. Crawler: s.Crawler,
  127. IgnoreEntryUpdates: s.IgnoreEntryUpdates,
  128. Disabled: s.Disabled,
  129. NoMediaPlayer: s.NoMediaPlayer,
  130. IgnoreHTTPCache: s.IgnoreHTTPCache,
  131. AllowSelfSignedCertificates: s.AllowSelfSignedCertificates,
  132. FetchViaProxy: s.FetchViaProxy,
  133. HideGlobally: s.HideGlobally,
  134. DisableHTTP2: s.DisableHTTP2,
  135. ScraperRules: s.ScraperRules,
  136. RewriteRules: s.RewriteRules,
  137. BlocklistRules: s.BlocklistRules,
  138. KeeplistRules: s.KeeplistRules,
  139. BlockFilterEntryRules: s.BlockFilterEntryRules,
  140. KeepFilterEntryRules: s.KeepFilterEntryRules,
  141. UrlRewriteRules: s.UrlRewriteRules,
  142. }
  143. if validationErr := validator.ValidateFeedCreation(store, userID, feedCreationRequest); validationErr != nil {
  144. return validationErr.Error()
  145. }
  146. return nil
  147. }
  148. // NewHandler creates a new handler for OPML files.
  149. func NewHandler(store *storage.Storage) *Handler {
  150. return &Handler{store: store}
  151. }