diff --git a/changelog.d/rss.fix b/changelog.d/rss.fix
new file mode 100644
index 000000000..a3455698a
--- /dev/null
+++ b/changelog.d/rss.fix
@@ -0,0 +1 @@
+Fix RSS feeds to be W3C compliant and render better in RSS readers
diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex
index e1ee33d62..a433b8403 100644
--- a/lib/pleroma/web/feed/feed_view.ex
+++ b/lib/pleroma/web/feed/feed_view.ex
@@ -97,6 +97,64 @@ defmodule Pleroma.Web.Feed.FeedView do
end
end
+ def rss_content_encoded(data) do
+ data
+ |> feed_content_encoded()
+ |> String.replace("]]>", "]]>")
+ end
+
+ def atom_content_encoded(data), do: feed_content_encoded(data)
+
+ defp feed_content_encoded(data) do
+ base_content =
+ case activity_content(data) do
+ "" ->
+ data["summary"] || data["type"] ||
+ ""
+ |> escape()
+
+ content ->
+ content
+ end
+
+ attachments_html =
+ (data["attachment"] || [])
+ |> Enum.map(&rss_attachment_preview/1)
+ |> Enum.reject(&(&1 == ""))
+ |> Enum.join("
")
+
+ [base_content, attachments_html]
+ |> Enum.reject(&(&1 == ""))
+ |> Enum.join("
")
+ end
+
+ defp rss_attachment_preview(attachment) do
+ href = attachment_href(attachment)
+
+ if is_binary(href) and href != "" do
+ media_type = attachment_type(attachment) || "application/octet-stream"
+ escaped_href = escape(href)
+ name = attachment["name"] || "Attachment"
+ escaped_name = escape(name)
+
+ cond do
+ String.starts_with?(media_type, "image/") ->
+ ~s(

)
+
+ String.starts_with?(media_type, "video/") ->
+ ~s(#{escaped_name}
)
+
+ String.starts_with?(media_type, "audio/") ->
+ ~s(#{escaped_name}
)
+
+ true ->
+ ~s(#{escaped_name}
)
+ end
+ else
+ ""
+ end
+ end
+
def activity_content(%{"content" => content}) do
content
|> String.replace(~r/[\n\r]/, "")
@@ -106,16 +164,99 @@ defmodule Pleroma.Web.Feed.FeedView do
def activity_context(activity), do: escape(activity.data["context"])
+ def feed_self_url(conn), do: Phoenix.Controller.current_url(conn)
+
+ def activity_link(activity, data) do
+ cond do
+ activity.local -> data["id"] || data["url"]
+ true -> data["external_url"] || data["id"] || data["url"]
+ end
+ end
+
def attachment_href(attachment) do
- attachment["url"]
- |> hd()
+ attachment
+ |> attachment_url_data()
|> Map.get("href")
end
def attachment_type(attachment) do
- attachment["url"]
- |> hd()
- |> Map.get("mediaType")
+ attachment
+ |> attachment_url_data()
+ |> Map.get("mediaType") || attachment["mediaType"]
+ end
+
+ def attachment_size(attachment) do
+ attachment
+ |> attachment_url_data()
+ |> Map.get("size", 0)
+ end
+
+ def attachment_size_positive(attachment) do
+ case attachment_size(attachment) do
+ size when is_integer(size) and size > 0 ->
+ size
+
+ size when is_binary(size) ->
+ case Integer.parse(size) do
+ {parsed, ""} when parsed > 0 -> parsed
+ _ -> nil
+ end
+
+ _ ->
+ nil
+ end
+ end
+
+ def attachment_medium(attachment) do
+ case attachment_type(attachment) do
+ "image/" <> _rest -> "image"
+ "video/" <> _rest -> "video"
+ "audio/" <> _rest -> "audio"
+ _ -> "document"
+ end
+ end
+
+ def attachment_previewable?(attachment) do
+ attachment_medium(attachment) in ["image", "video", "audio"]
+ end
+
+ def attachment_description(attachment) do
+ attachment["name"] || ""
+ end
+
+ def media_content_xml(attachment) do
+ href = escape(attachment_href(attachment) || "")
+ type = escape(attachment_type(attachment) || "application/octet-stream")
+ medium = escape(attachment_medium(attachment))
+
+ file_size_attr =
+ case attachment_size_positive(attachment) do
+ size when is_integer(size) -> ~s( fileSize="#{size}")
+ _ -> ""
+ end
+
+ description_xml =
+ case attachment_description(attachment) do
+ "" ->
+ ""
+
+ description ->
+ ~s(\n #{escape(description)})
+ end
+
+ """
+
+ nonadult#{description_xml}
+
+ """
+ end
+
+ defp attachment_url_data(attachment) do
+ case attachment["url"] do
+ [first | _] when is_map(first) -> first
+ %{} = map -> map
+ _ -> %{}
+ end
end
def get_href(id) do
diff --git a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
index b774f7984..f87d97cb4 100644
--- a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
@@ -3,7 +3,7 @@
http://activitystrea.ms/schema/1.0/post
<%= @data["id"] %>
<%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %>
- <%= activity_description(@data) %>
+ <%= escape(atom_content_encoded(@data)) %>
<%= to_rfc3339(@data["published"]) %>
<%= to_rfc3339(@data["published"]) %>
@@ -15,35 +15,23 @@
<%= escape(@data["summary"]) %>
<% end %>
- <%= if @activity.local do %>
-
-
- <% else %>
-
- <% end %>
+
+
<%= for tag <- Pleroma.Object.hashtags(@object) do %>
<% end %>
<%= for attachment <- @data["attachment"] || [] do %>
-
+ <%= unless attachment_previewable?(attachment) do %>
+
+ <% end %>
<% end %>
<%= if @data["inReplyTo"] do %>
<% end %>
- <%= for id <- @activity.recipients do %>
- <%= if id == Pleroma.Constants.as_public() do %>
-
- <% else %>
- <%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>
-
- <% end %>
- <% end %>
- <% end %>
-
<%= for {emoji, file} <- @data["emoji"] || %{} do %>
<% end %>
diff --git a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
index 7de98f736..a8c2aa342 100644
--- a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
+++ b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
@@ -1,45 +1,32 @@
-
http://activitystrea.ms/schema/1.0/note
http://activitystrea.ms/schema/1.0/post
- <%= @data["id"] %>
+ <%= @data["id"] %>
<%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %>
<%= activity_description(@data) %>
+ ]]>
<%= to_rfc2822(@data["published"]) %>
<%= activity_context(@activity) %>
- <%= if @activity.local do %>
- <%= @data["id"] %>
- <% else %>
- <%= @data["external_url"] %>
- <% end %>
-
- <%= activity_context(@activity) %>
+ <%= activity_link(@activity, @data) %>
<%= for tag <- Pleroma.Object.hashtags(@object) do %>
-
+ <%= if tag && tag != "" do %>
+ <%= tag %>
+ <% end %>
<% end %>
<%= for attachment <- @data["attachment"] || [] do %>
-
+ <%= size = attachment_size_positive(attachment) %>
+ <%= unless attachment_previewable?(attachment) do %>
+
+ <% end %>
+ <%= media_content_xml(attachment) %>
<% end %>
<%= if @data["inReplyTo"] do %>
<% end %>
-
- <%= for id <- @activity.recipients do %>
- <%= if id == Pleroma.Constants.as_public() do %>
- http://activityschema.org/collection/public
- <% else %>
- <%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>
- <%= id %>
- <% end %>
- <% end %>
- <% end %>
-
- <%= for {emoji, file} <- @data["emoji"] || %{} do %>
- <%= file %>
- <% end %>
diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
index 03c222975..ae762f9ed 100644
--- a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
@@ -6,7 +6,7 @@
<%= @data["id"] %>
<%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %>
- <%= activity_description(@data) %>
+ <%= escape(atom_content_encoded(@data)) %>
<%= to_rfc3339(@data["published"]) %>
<%= to_rfc3339(@data["published"]) %>
@@ -15,34 +15,22 @@
<%= if @data["summary"] != "" do %>
- <%= @data["summary"] %>
+ <%= escape(@data["summary"]) %>
<% end %>
- <%= if @activity.local do %>
-
-
- <% else %>
-
- <% end %>
-
- <%= for id <- @activity.recipients do %>
- <%= if id == Pleroma.Constants.as_public() do %>
-
- <% else %>
- <%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>
-
- <% end %>
- <% end %>
- <% end %>
+
+
<%= for tag <- Pleroma.Object.hashtags(@object) do %>
<% end %>
+ <%= for attachment <- @data["attachment"] || [] do %>
+ <%= unless attachment_previewable?(attachment) do %>
+
+ <% end %>
+ <% end %>
+
<%= for {emoji, file} <- @data["emoji"] || %{} do %>
<% end %>
diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex
index 1b8c34b87..6ae3c5a55 100644
--- a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex
+++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex
@@ -1,14 +1,22 @@
-
- <%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %>
-
-
- <%= activity_context(@activity) %>
- <%= activity_context(@activity) %>
+ <%= activity_link(@activity, @data) %>
+ <%= activity_link(@activity, @data) %>
<%= to_rfc2822(@data["published"]) %>
-
+ <%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %>
<%= activity_description(@data) %>
+ ]]>
+
<%= for attachment <- @data["attachment"] || [] do %>
-
+ <%= size = attachment_size_positive(attachment) %>
+ <%= unless attachment_previewable?(attachment) do %>
+
+ <% end %>
+ <%= media_content_xml(attachment) %>
<% end %>
+ <%= for tag <- Pleroma.Object.hashtags(@object) do %>
+ <%= if tag && tag != "" do %>
+ <%= tag %>
+ <% end %>
+ <% end %>
diff --git a/lib/pleroma/web/templates/feed/feed/tag.atom.eex b/lib/pleroma/web/templates/feed/feed/tag.atom.eex
index 3449c97ff..1646aeec7 100644
--- a/lib/pleroma/web/templates/feed/feed/tag.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/tag.atom.eex
@@ -9,10 +9,12 @@
<%= Routes.tag_feed_url(@conn, :feed, @tag) <> ".atom" %>
#<%= @tag %>
- <%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %>
+ <%= with subtitle when subtitle not in [nil, ""] <- Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) do %>
+ <%= subtitle %>
+ <% end %>
<%= feed_logo() %>
<%= most_recent_update(@activities) %>
- " type="application/atom+xml"/>
+
<%= for activity <- @activities do %>
<%= render Phoenix.Controller.view_module(@conn), "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %>
diff --git a/lib/pleroma/web/templates/feed/feed/tag.rss.eex b/lib/pleroma/web/templates/feed/feed/tag.rss.eex
index a87f9bf50..618b89ba3 100644
--- a/lib/pleroma/web/templates/feed/feed/tag.rss.eex
+++ b/lib/pleroma/web/templates/feed/feed/tag.rss.eex
@@ -1,6 +1,8 @@
@@ -8,7 +10,10 @@
<%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %>
<%= "#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss" %>
<%= feed_logo() %>
+ <%= feed_logo() %>
2b90d9
+ <%= most_recent_update(@activities) || to_rfc2822(NaiveDateTime.utc_now()) %>
+ <%= Pleroma.Application.named_version() %>
<%= for activity <- @activities do %>
<%= render Phoenix.Controller.view_module(@conn), "_tag_activity.xml", Map.merge(assigns, prepare_activity(activity)) %>
<% end %>
diff --git a/lib/pleroma/web/templates/feed/feed/user.atom.eex b/lib/pleroma/web/templates/feed/feed/user.atom.eex
index c2c77cfed..cc6883727 100644
--- a/lib/pleroma/web/templates/feed/feed/user.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/user.atom.eex
@@ -8,10 +8,12 @@
<%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %>
<%= @user.nickname <> "'s timeline" %>
- <%= escape(@user.bio) %>
+ <%= if @user.bio && @user.bio != "" do %>
+ <%= escape(@user.bio) %>
+ <% end %>
<%= most_recent_update(@activities, @user, :atom) %>
<%= logo(@user) %>
- " type="application/atom+xml"/>
+
<%= render Phoenix.Controller.view_module(@conn), "_author.atom", assigns %>
diff --git a/lib/pleroma/web/templates/feed/feed/user.rss.eex b/lib/pleroma/web/templates/feed/feed/user.rss.eex
index b907a7e57..e2150d8b6 100644
--- a/lib/pleroma/web/templates/feed/feed/user.rss.eex
+++ b/lib/pleroma/web/templates/feed/feed/user.rss.eex
@@ -1,26 +1,32 @@
<%= @user.nickname <> "'s timeline" %>
- <%= "#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss" %>
- "
+
+ <%= "#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss" %>
<%= escape(@user.bio) %>
<%= logo(@user) %>
<%= @user.nickname <> "'s timeline" %>
<%= "#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss" %>
+ <%= most_recent_update(@activities, @user, :rss) %>
+ <%= logo(@user) %>
+ <%= Pleroma.Application.named_version() %>
<%= render Phoenix.Controller.view_module(@conn), "_author.rss", assigns %>
<%= if last_activity(@activities) do %>
- <%= "#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss?max_id=#{last_activity(@activities).id}" %>
+ " />
<% end %>
<%= for activity <- @activities do %>
diff --git a/test/pleroma/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs
index 662235f31..7f2f57b07 100644
--- a/test/pleroma/web/feed/tag_controller_test.exs
+++ b/test/pleroma/web/feed/tag_controller_test.exs
@@ -141,7 +141,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
FeedView.to_rfc2822(activity1.data["published"])
]
- assert xpath(xml, ~x"//channel/item/enclosure/@url"sl) == [
+ assert xpath(xml, ~x"//channel/item/media:content/@url"sl) == [
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4"
]