opml.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  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. "encoding/xml"
  6. "fmt"
  7. "strconv"
  8. "strings"
  9. )
  10. const minifluxOPMLNamespace = "https://miniflux.app/opml"
  11. // Specs: http://opml.org/spec2.opml
  12. type opmlDocument struct {
  13. XMLName xml.Name `xml:"opml"`
  14. Version string `xml:"version,attr"`
  15. MinifluxNamespace string `xml:"xmlns:miniflux,attr,omitempty"`
  16. Header opmlHeader `xml:"head"`
  17. Outlines opmlOutlineCollection `xml:"body>outline"`
  18. }
  19. type opmlHeader struct {
  20. Title string `xml:"title,omitempty"`
  21. DateCreated string `xml:"dateCreated,omitempty"`
  22. OwnerName string `xml:"ownerName,omitempty"`
  23. }
  24. type opmlOutline struct {
  25. Title string `xml:"title,attr,omitempty"`
  26. Text string `xml:"text,attr"`
  27. FeedURL string `xml:"xmlUrl,attr,omitempty"`
  28. SiteURL string `xml:"htmlUrl,attr,omitempty"`
  29. Description string `xml:"description,attr,omitempty"`
  30. Outlines opmlOutlineCollection `xml:"outline,omitempty"`
  31. // Miniflux-specific feed settings
  32. ScraperRules string `xml:"miniflux:scraperRules,attr,omitempty"`
  33. RewriteRules string `xml:"miniflux:rewriteRules,attr,omitempty"`
  34. UrlRewriteRules string `xml:"miniflux:urlRewriteRules,attr,omitempty"`
  35. BlocklistRules string `xml:"miniflux:blocklistRules,attr,omitempty"`
  36. KeeplistRules string `xml:"miniflux:keeplistRules,attr,omitempty"`
  37. BlockFilterEntryRules string `xml:"miniflux:blockFilterEntryRules,attr,omitempty"`
  38. KeepFilterEntryRules string `xml:"miniflux:keepFilterEntryRules,attr,omitempty"`
  39. UserAgent string `xml:"miniflux:userAgent,attr,omitempty"`
  40. Crawler bool `xml:"miniflux:crawler,attr,omitempty"`
  41. IgnoreHTTPCache bool `xml:"miniflux:ignoreHTTPCache,attr,omitempty"`
  42. FetchViaProxy bool `xml:"miniflux:fetchViaProxy,attr,omitempty"`
  43. Disabled bool `xml:"miniflux:disabled,attr,omitempty"`
  44. NoMediaPlayer bool `xml:"miniflux:noMediaPlayer,attr,omitempty"`
  45. HideGlobally bool `xml:"miniflux:hideGlobally,attr,omitempty"`
  46. AllowSelfSignedCertificates bool `xml:"miniflux:allowSelfSignedCertificates,attr,omitempty"`
  47. DisableHTTP2 bool `xml:"miniflux:disableHTTP2,attr,omitempty"`
  48. IgnoreEntryUpdates bool `xml:"miniflux:ignoreEntryUpdates,attr,omitempty"`
  49. }
  50. func (o opmlOutline) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
  51. type opmlOutlineXml opmlOutline
  52. outlineType := ""
  53. if o.IsSubscription() {
  54. outlineType = "rss"
  55. }
  56. return e.EncodeElement(struct {
  57. opmlOutlineXml
  58. Type string `xml:"type,attr,omitempty"`
  59. }{
  60. opmlOutlineXml: opmlOutlineXml(o),
  61. Type: outlineType,
  62. }, start)
  63. }
  64. func (o *opmlOutline) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
  65. *o = opmlOutline{}
  66. type opmlOutlineAlias opmlOutline
  67. if err := d.DecodeElement((*opmlOutlineAlias)(o), &start); err != nil {
  68. return err
  69. }
  70. for _, attr := range start.Attr {
  71. if attr.Name.Space == minifluxOPMLNamespace {
  72. if err := o.setMinifluxAttribute(attr.Name.Local, attr.Value); err != nil {
  73. return err
  74. }
  75. }
  76. }
  77. return nil
  78. }
  79. func (o opmlOutline) IsSubscription() bool {
  80. return strings.TrimSpace(o.FeedURL) != ""
  81. }
  82. func (o opmlOutline) GetTitle() string {
  83. if o.Title != "" {
  84. return o.Title
  85. }
  86. if o.Text != "" {
  87. return o.Text
  88. }
  89. if o.SiteURL != "" {
  90. return o.SiteURL
  91. }
  92. if o.FeedURL != "" {
  93. return o.FeedURL
  94. }
  95. return ""
  96. }
  97. func (o opmlOutline) GetSiteURL() string {
  98. if o.SiteURL != "" {
  99. return o.SiteURL
  100. }
  101. return o.FeedURL
  102. }
  103. type opmlOutlineCollection []opmlOutline
  104. func (o opmlOutlineCollection) HasChildren() bool {
  105. return len(o) > 0
  106. }
  107. func (o *opmlOutline) setMinifluxAttribute(name, value string) error {
  108. switch name {
  109. case "scraperRules":
  110. o.ScraperRules = value
  111. case "rewriteRules":
  112. o.RewriteRules = value
  113. case "urlRewriteRules":
  114. o.UrlRewriteRules = value
  115. case "blocklistRules":
  116. o.BlocklistRules = value
  117. case "keeplistRules":
  118. o.KeeplistRules = value
  119. case "blockFilterEntryRules":
  120. o.BlockFilterEntryRules = value
  121. case "keepFilterEntryRules":
  122. o.KeepFilterEntryRules = value
  123. case "userAgent":
  124. o.UserAgent = value
  125. case "crawler":
  126. return setMinifluxBoolAttribute(name, value, &o.Crawler)
  127. case "ignoreHTTPCache":
  128. return setMinifluxBoolAttribute(name, value, &o.IgnoreHTTPCache)
  129. case "fetchViaProxy":
  130. return setMinifluxBoolAttribute(name, value, &o.FetchViaProxy)
  131. case "disabled":
  132. return setMinifluxBoolAttribute(name, value, &o.Disabled)
  133. case "noMediaPlayer":
  134. return setMinifluxBoolAttribute(name, value, &o.NoMediaPlayer)
  135. case "hideGlobally":
  136. return setMinifluxBoolAttribute(name, value, &o.HideGlobally)
  137. case "allowSelfSignedCertificates":
  138. return setMinifluxBoolAttribute(name, value, &o.AllowSelfSignedCertificates)
  139. case "disableHTTP2":
  140. return setMinifluxBoolAttribute(name, value, &o.DisableHTTP2)
  141. case "ignoreEntryUpdates":
  142. return setMinifluxBoolAttribute(name, value, &o.IgnoreEntryUpdates)
  143. }
  144. return nil
  145. }
  146. func setMinifluxBoolAttribute(name, value string, target *bool) error {
  147. parsedValue, err := strconv.ParseBool(value)
  148. if err != nil {
  149. return fmt.Errorf("opml: invalid miniflux attribute %q: %w", name, err)
  150. }
  151. *target = parsedValue
  152. return nil
  153. }