From 3f0783c0a50c99ba4697829f9571b57d8b08f5de Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Mon, 14 Nov 2022 01:50:52 +0500 Subject: [PATCH 01/10] fix atom and rss feeds for users and tags Changes: - make the XML closer to spec (RSS does not pass w3c's validator, but works) - fix dates (RFC3339 for Atom, doc says RFC822 for RSS but RFC1123 is closer) - fix attachment/enclosure links (but see below) - set feed item title to post's "summary" if present - pruned several elements that validators did not like - examples: ap_enabled, user banner urls. Specs: - https://www.rssboard.org/rss-specification - https://validator.w3.org/feed/docs/atom.html - https://www.intertwingly.net/wiki/pie/Rss20AndAtom10Compared Validators: - https://validator.w3.org/feed/ - https://rssatom.com/feedvalidator.php Attachment/enclosure links should have a "length" field (mandatory according to the spec). This is not present in the object's data map. --- lib/pleroma/web/feed/feed_view.ex | 71 +++++++++++++++++-- .../templates/feed/feed/_activity.atom.eex | 8 +-- .../web/templates/feed/feed/_activity.rss.eex | 11 +-- .../web/templates/feed/feed/_author.atom.eex | 19 +++-- .../web/templates/feed/feed/_author.rss.eex | 27 +++---- .../feed/feed/_tag_activity.atom.eex | 64 ++++++++--------- .../templates/feed/feed/_tag_author.atom.eex | 28 ++++---- .../web/templates/feed/feed/tag.atom.eex | 32 ++++----- .../web/templates/feed/feed/user.atom.eex | 4 +- .../web/templates/feed/feed/user.rss.eex | 17 +++-- 10 files changed, 162 insertions(+), 119 deletions(-) diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 35a5f9482..323ede90a 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -21,7 +21,7 @@ def pub_date(date) when is_binary(date) do |> pub_date end - def pub_date(%DateTime{} = date), do: Timex.format!(date, "{RFC822}") + def pub_date(%DateTime{} = date), do: to_rfc1123(date) def prepare_activity(activity, opts \\ []) do object = Object.normalize(activity, fetch: false) @@ -41,13 +41,18 @@ def prepare_activity(activity, opts \\ []) do def most_recent_update(activities) do with %{updated_at: updated_at} <- List.first(activities) do - NaiveDateTime.to_iso8601(updated_at) + to_rfc3339(updated_at) end end - def most_recent_update(activities, user) do + def most_recent_update(activities, user, :atom) do (List.first(activities) || user).updated_at - |> NaiveDateTime.to_iso8601() + |> to_rfc3339() + end + + def most_recent_update(activities, user, :rss) do + (List.first(activities) || user).updated_at + |> to_rfc1123() end def feed_logo do @@ -61,6 +66,10 @@ def feed_logo do |> MediaProxy.url() end + def email(user) do + user.nickname <> "@" <> Pleroma.Web.Endpoint.host() + end + def logo(user) do user |> User.avatar_url() @@ -69,18 +78,35 @@ def logo(user) do def last_activity(activities), do: List.last(activities) - def activity_title(%{"content" => content}, opts \\ %{}) do - content + def activity_title(%{"content" => content, "summary" => summary} = data, opts \\ %{}) do + title = + cond do + summary != "" -> summary + content != "" -> activity_content(data) + true -> "a post" + end + + title |> Pleroma.Web.Metadata.Utils.scrub_html() |> Pleroma.Emoji.Formatter.demojify() |> Formatter.truncate(opts[:max_length], opts[:omission]) |> escape() end + def activity_description(data) do + content = activity_content(data) + summary = data["summary"] + + cond do + content != "" -> escape(content) + summary != "" -> escape(summary) + true -> escape(data["type"]) + end + end + def activity_content(%{"content" => content}) do content |> String.replace(~r/[\n\r]/, "") - |> escape() end def activity_content(_), do: "" @@ -112,4 +138,35 @@ def escape(html) do |> html_escape() |> safe_to_string() end + + @spec to_rfc3339(String.t() | NativeDateTime.t()) :: String.t() + def to_rfc3339(date) when is_binary(date) do + date + |> Timex.parse!("{ISO:Extended}") + |> to_rfc3339() + end + + def to_rfc3339(nd) do + nd + |> Timex.to_datetime() + |> Timex.format!("{RFC3339}") + end + + @spec to_rfc1123(String.t() | DateTime.t() | NativeDateTime.t()) :: String.t() + def to_rfc1123(datestr) when is_binary(datestr) do + datestr + |> Timex.parse!("{ISO:Extended}") + |> to_rfc1123() + end + + def to_rfc1123(%DateTime{} = date) do + date + |> Timex.format!("{RFC1123}") + end + + def to_rfc1123(nd) do + nd + |> Timex.to_datetime() + |> Timex.format!("{RFC1123}") + end end diff --git a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex index 57bd92468..260338772 100644 --- a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex @@ -3,15 +3,15 @@ http://activitystrea.ms/schema/1.0/post <%= @data["id"] %> <%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %> - <%= activity_content(@data) %> - <%= @activity.data["published"] %> - <%= @activity.data["published"] %> + <%= activity_description(@data) %> + <%= to_rfc3339(@activity.data["published"]) %> + <%= to_rfc3339(@activity.data["published"]) %> <%= activity_context(@activity) %> - <%= if @data["summary"] do %> + <%= if @data["summary"] != "" do %> <%= escape(@data["summary"]) %> <% end %> diff --git a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex index 279f2171d..7a7e494c0 100644 --- a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex @@ -3,17 +3,12 @@ http://activitystrea.ms/schema/1.0/post <%= @data["id"] %> <%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %> - <%= activity_content(@data) %> - <%= @activity.data["published"] %> - <%= @activity.data["published"] %> + <%= activity_description(@data) %> + <%= to_rfc1123(@activity.data["published"]) %> <%= activity_context(@activity) %> - <%= if @data["summary"] do %> - <%= escape(@data["summary"]) %> - <% end %> - <%= if @activity.local do %> <%= @data["id"] %> <% else %> @@ -27,7 +22,7 @@ <% end %> <%= for attachment <- @data["attachment"] || [] do %> - <%= attachment_href(attachment) %> + <% end %> <%= if @data["inReplyTo"] do %> diff --git a/lib/pleroma/web/templates/feed/feed/_author.atom.eex b/lib/pleroma/web/templates/feed/feed/_author.atom.eex index 25cbffada..90be8a559 100644 --- a/lib/pleroma/web/templates/feed/feed/_author.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/_author.atom.eex @@ -1,17 +1,14 @@ - <%= @user.ap_id %> - http://activitystrea.ms/schema/1.0/person <%= @user.ap_id %> + <%= @user.nickname %> + http://activitystrea.ms/schema/1.0/person + <%= @user.name %> + <%= User.avatar_url(@user) %> + <%= @user.ap_id %> + <%= to_rfc3339(@user.inserted_at) %> + <%= to_rfc3339(@user.updated_at) %> + <%= @user.ap_id %> <%= @user.nickname %> <%= @user.name %> <%= escape(@user.bio) %> - <%= escape(@user.bio) %> - <%= @user.nickname %> - - <%= if User.banner_url(@user) do %> - - <% end %> - <%= if @user.local do %> - true - <% end %> diff --git a/lib/pleroma/web/templates/feed/feed/_author.rss.eex b/lib/pleroma/web/templates/feed/feed/_author.rss.eex index 526aeddcf..22477e6b1 100644 --- a/lib/pleroma/web/templates/feed/feed/_author.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/_author.rss.eex @@ -1,17 +1,10 @@ - - <%= @user.ap_id %> - http://activitystrea.ms/schema/1.0/person - <%= @user.ap_id %> - <%= @user.nickname %> - <%= @user.name %> - <%= escape(@user.bio) %> - <%= escape(@user.bio) %> - <%= @user.nickname %> - <%= User.avatar_url(@user) %> - <%= if User.banner_url(@user) do %> - <%= User.banner_url(@user) %> - <% end %> - <%= if @user.local do %> - true - <% end %> - +<%= "#{email(@user)} (#{escape(@user.name)})" %> +http://activitystrea.ms/schema/1.0/person +<%= @user.name %> +<%= User.avatar_url(@user) %> +<%= @user.ap_id %> +<%= to_rfc3339(@user.inserted_at) %> +<%= to_rfc3339(@user.updated_at) %> +<%= @user.nickname %> +<%= @user.name %> +<%= escape(@user.bio) %> 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 7e2e587e1..25980c1e4 100644 --- a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex @@ -1,12 +1,22 @@ - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post + http://activitystrea.ms/schema/1.0/note + http://activitystrea.ms/schema/1.0/post <%= render Phoenix.Controller.view_module(@conn), "_tag_author.atom", assigns %> - <%= @data["id"] %> - <%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %> - <%= activity_content(@data) %> + <%= @data["id"] %> + <%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %> + <%= activity_description(@data) %> + <%= to_rfc3339(@activity.data["published"]) %> + <%= to_rfc3339(@activity.data["published"]) %> + + <%= activity_context(@activity) %> + + + + <%= if @data["summary"] != "" do %> + <%= @data["summary"] %> + <% end %> <%= if @activity.local do %> @@ -15,37 +25,25 @@ <% end %> - <%= @activity.data["published"] %> - <%= @activity.data["published"] %> - - - <%= activity_context(@activity) %> - - - - <%= if @data["summary"] do %> - <%= @data["summary"] %> - <% end %> - - <%= for id <- @activity.recipients do %> - <%= if id == Pleroma.Constants.as_public() do %> + <%= for id <- @activity.recipients do %> + <%= if id == Pleroma.Constants.as_public() do %> + + <% else %> + <%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %> - <% else %> - <%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %> - - <% end %> + ostatus:object-type="http://activitystrea.ms/schema/1.0/person" + href="<%= id %>" /> <% end %> <% end %> + <% end %> - <%= for tag <- Pleroma.Object.hashtags(@object) do %> - - <% end %> + <%= for tag <- Pleroma.Object.hashtags(@object) do %> + + <% end %> - <%= for {emoji, file} <- @data["emoji"] || %{} do %> - - <% end %> + <%= for {emoji, file} <- @data["emoji"] || %{} do %> + + <% end %> diff --git a/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex index 997c4936e..71c696832 100644 --- a/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex @@ -1,18 +1,14 @@ - http://activitystrea.ms/schema/1.0/person - <%= @actor.ap_id %> - <%= @actor.ap_id %> - <%= @actor.nickname %> - <%= escape(@actor.bio) %> - - <%= if User.banner_url(@actor) do %> - - <% end %> - <%= if @actor.local do %> - true - <% end %> - - <%= @actor.nickname %> - <%= @actor.name %> - <%= escape(@actor.bio) %> + <%= @actor.ap_id %> + <%= @actor.nickname %> + http://activitystrea.ms/schema/1.0/person + <%= @actor.name %> + <%= User.avatar_url(@actor) %> + <%= @actor.ap_id %> + <%= to_rfc3339(@actor.inserted_at) %> + <%= to_rfc3339(@actor.updated_at) %> + <%= @actor.ap_id %> + <%= @actor.nickname %> + <%= @actor.name %> + <%= escape(@actor.bio) %> diff --git a/lib/pleroma/web/templates/feed/feed/tag.atom.eex b/lib/pleroma/web/templates/feed/feed/tag.atom.eex index 8c551feaf..4955996c9 100644 --- a/lib/pleroma/web/templates/feed/feed/tag.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/tag.atom.eex @@ -1,22 +1,20 @@ - + - <%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %> - #<%= @tag %> + <%= 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) %> + <%= feed_logo() %> + <%= most_recent_update(@activities) %> + - <%= 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) %> - <%= feed_logo() %> - <%= most_recent_update(@activities) %> - - <%= for activity <- @activities do %> - <%= render Phoenix.Controller.view_module(@conn), "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %> - <% end %> + <%= for activity <- @activities do %> + <%= render Phoenix.Controller.view_module(@conn), "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %> + <% end %> diff --git a/lib/pleroma/web/templates/feed/feed/user.atom.eex b/lib/pleroma/web/templates/feed/feed/user.atom.eex index 97a7535ab..a0148d768 100644 --- a/lib/pleroma/web/templates/feed/feed/user.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/user.atom.eex @@ -1,14 +1,14 @@ <%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %> <%= @user.nickname <> "'s timeline" %> - <%= most_recent_update(@activities, @user) %> + <%= escape(@user.bio) %> + <%= most_recent_update(@activities, @user, :atom) %> <%= logo(@user) %> diff --git a/lib/pleroma/web/templates/feed/feed/user.rss.eex b/lib/pleroma/web/templates/feed/feed/user.rss.eex index a9fee244c..80ad8b161 100644 --- a/lib/pleroma/web/templates/feed/feed/user.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/user.rss.eex @@ -1,11 +1,20 @@ - + - <%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".rss" %> <%= @user.nickname <> "'s timeline" %> - <%= most_recent_update(@activities, @user) %> - <%= logo(@user) %> <%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss' %> + " + rel="self" type="application/rss+xml" /> + <%= escape(@user.bio) %> + + <%= logo(@user) %> + <%= @user.nickname <> "'s timeline" %> + <%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss' %> + <%= render Phoenix.Controller.view_module(@conn), "_author.rss", assigns %> From f3253c0c6a0f6350437fc701023b375ecb1b7bc6 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 15 Nov 2022 11:45:32 -0500 Subject: [PATCH 02/10] Implement RFC2822 timestamp formatting --- lib/pleroma/web/feed/feed_view.ex | 46 +++++++++++++++---- .../web/templates/feed/feed/_activity.rss.eex | 2 +- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 323ede90a..35136897f 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -14,6 +14,9 @@ defmodule Pleroma.Web.Feed.FeedView do require Pleroma.Constants + @days ~w(Mon Tue Wed Thu Fri Sat Sun) + @months ~w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec) + @spec pub_date(String.t() | DateTime.t()) :: String.t() def pub_date(date) when is_binary(date) do date @@ -21,7 +24,7 @@ def pub_date(date) when is_binary(date) do |> pub_date end - def pub_date(%DateTime{} = date), do: to_rfc1123(date) + def pub_date(%DateTime{} = date), do: to_rfc2822(date) def prepare_activity(activity, opts \\ []) do object = Object.normalize(activity, fetch: false) @@ -52,7 +55,7 @@ def most_recent_update(activities, user, :atom) do def most_recent_update(activities, user, :rss) do (List.first(activities) || user).updated_at - |> to_rfc1123() + |> to_rfc2822() end def feed_logo do @@ -152,21 +155,46 @@ def to_rfc3339(nd) do |> Timex.format!("{RFC3339}") end - @spec to_rfc1123(String.t() | DateTime.t() | NativeDateTime.t()) :: String.t() - def to_rfc1123(datestr) when is_binary(datestr) do + @spec to_rfc2822(String.t() | DateTime.t() | NativeDateTime.t()) :: String.t() + def to_rfc2822(datestr) when is_binary(datestr) do datestr |> Timex.parse!("{ISO:Extended}") - |> to_rfc1123() + |> to_rfc2822() end - def to_rfc1123(%DateTime{} = date) do + def to_rfc2822(%DateTime{} = date) do date - |> Timex.format!("{RFC1123}") + |> DateTime.to_naive() + |> NaiveDateTime.to_erl() + |> rfc2822_from_erl() end - def to_rfc1123(nd) do + def to_rfc2822(nd) do nd |> Timex.to_datetime() - |> Timex.format!("{RFC1123}") + |> DateTime.to_naive() + |> NaiveDateTime.to_erl() + |> rfc2822_from_erl() + end + + @doc """ + Builds a RFC2822 timestamp from an Erlang timestamp + [RFC2822 3.3 - Date and Time Specification](https://tools.ietf.org/html/rfc2822#section-3.3) + This function always assumes the Erlang timestamp is in Universal time, not Local time + """ + def rfc2822_from_erl({{year, month, day} = date, {hour, minute, second}}) do + day_name = Enum.at(@days, :calendar.day_of_the_week(date) - 1) + month_name = Enum.at(@months, month - 1) + + date_part = "#{day_name}, #{day} #{month_name} #{year}" + time_part = "#{pad(hour)}:#{pad(minute)}:#{pad(second)}" + + date_part <> " " <> time_part <> " +0000" + end + + defp pad(num) do + num + |> Integer.to_string() + |> String.pad_leading(2, "0") end end diff --git a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex index 7a7e494c0..5c8f35fe4 100644 --- a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex @@ -4,7 +4,7 @@ <%= @data["id"] %> <%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %> <%= activity_description(@data) %> - <%= to_rfc1123(@activity.data["published"]) %> + <%= to_rfc2822(@activity.data["published"]) %> <%= activity_context(@activity) %> From 8d500977a6ce9b0fd461c16ee2b343bab510e27f Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Tue, 15 Nov 2022 23:29:40 +0500 Subject: [PATCH 03/10] fix: feed item title was escaped twice --- lib/pleroma/web/feed/feed_view.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 35136897f..3e24397aa 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -93,7 +93,6 @@ def activity_title(%{"content" => content, "summary" => summary} = data, opts \\ |> Pleroma.Web.Metadata.Utils.scrub_html() |> Pleroma.Emoji.Formatter.demojify() |> Formatter.truncate(opts[:max_length], opts[:omission]) - |> escape() end def activity_description(data) do From 3f63caee2ade363719591edf7f6cb4c56587efda Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Tue, 15 Nov 2022 23:30:56 +0500 Subject: [PATCH 04/10] fix: add xmlns:thr for in-reply-to refs --- lib/pleroma/web/templates/feed/feed/tag.atom.eex | 2 +- lib/pleroma/web/templates/feed/feed/tag.rss.eex | 5 +++-- lib/pleroma/web/templates/feed/feed/user.atom.eex | 1 + lib/pleroma/web/templates/feed/feed/user.rss.eex | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/templates/feed/feed/tag.atom.eex b/lib/pleroma/web/templates/feed/feed/tag.atom.eex index 4955996c9..14b0ee594 100644 --- a/lib/pleroma/web/templates/feed/feed/tag.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/tag.atom.eex @@ -1,7 +1,7 @@ - - + - #<%= @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) %> <%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %> diff --git a/lib/pleroma/web/templates/feed/feed/user.atom.eex b/lib/pleroma/web/templates/feed/feed/user.atom.eex index a0148d768..e36bfc66c 100644 --- a/lib/pleroma/web/templates/feed/feed/user.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/user.atom.eex @@ -1,6 +1,7 @@ diff --git a/lib/pleroma/web/templates/feed/feed/user.rss.eex b/lib/pleroma/web/templates/feed/feed/user.rss.eex index 80ad8b161..fae3fcf3d 100644 --- a/lib/pleroma/web/templates/feed/feed/user.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/user.rss.eex @@ -1,6 +1,7 @@ From f597b1b3e6f04125422c0f7d8e076be44e578e48 Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Tue, 15 Nov 2022 23:42:12 +0500 Subject: [PATCH 05/10] remove ap_id test -- the element makes the feed break --- test/pleroma/web/feed/tag_controller_test.exs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/pleroma/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs index 1bd2b157c..a67acdb47 100644 --- a/test/pleroma/web/feed/tag_controller_test.exs +++ b/test/pleroma/web/feed/tag_controller_test.exs @@ -63,7 +63,6 @@ test "gets a feed (ATOM)", %{conn: conn} do ] assert xpath(xml, ~x"//feed/entry/author/name/text()"ls) == [user.nickname, user.nickname] - assert xpath(xml, ~x"//feed/entry/author/id/text()"ls) == [user.ap_id, user.ap_id] conn = conn From 0f67eab38483c574db82a00b53fb29e13c7df089 Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Tue, 15 Nov 2022 23:42:54 +0500 Subject: [PATCH 06/10] remove pub_date() -- use to_rfc2822 instead _tag_activity.xml.eex used activity_content() instead of activity_description(), and did not escape html properly. --- lib/pleroma/web/feed/feed_view.ex | 9 --------- .../web/templates/feed/feed/_tag_activity.xml.eex | 4 ++-- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 3e24397aa..449659f4b 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -17,15 +17,6 @@ defmodule Pleroma.Web.Feed.FeedView do @days ~w(Mon Tue Wed Thu Fri Sat Sun) @months ~w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec) - @spec pub_date(String.t() | DateTime.t()) :: String.t() - def pub_date(date) when is_binary(date) do - date - |> Timex.parse!("{ISO:Extended}") - |> pub_date - end - - def pub_date(%DateTime{} = date), do: to_rfc2822(date) - def prepare_activity(activity, opts \\ []) do object = Object.normalize(activity, fetch: false) 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 2334e24a2..d582c83e8 100644 --- a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex @@ -4,9 +4,9 @@ <%= activity_context(@activity) %> <%= activity_context(@activity) %> - <%= pub_date(@activity.data["published"]) %> + <%= to_rfc2822(@activity.data["published"]) %> - <%= activity_content(@data) %> + <%= activity_description(@data) %> <%= for attachment <- @data["attachment"] || [] do %> <% end %> From c49316faee3d49df6236c8a57d0ed5c26d7d528e Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Tue, 15 Nov 2022 23:45:08 +0500 Subject: [PATCH 07/10] modify user feed controller test to expect summary for title --- test/pleroma/web/feed/user_controller_test.exs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs index 38cde3315..de32d3d4b 100644 --- a/test/pleroma/web/feed/user_controller_test.exs +++ b/test/pleroma/web/feed/user_controller_test.exs @@ -74,7 +74,7 @@ test "gets an atom feed", %{conn: conn, user: user, object: object, max_id: max_ |> SweetXml.parse() |> SweetXml.xpath(~x"//entry/title/text()"l) - assert activity_titles == ['42 & Thi...', 'This & t...'] + assert activity_titles == ['2hu', '2hu & as'] assert resp =~ FeedView.escape(object.data["content"]) assert resp =~ FeedView.escape(object.data["summary"]) assert resp =~ FeedView.escape(object.data["context"]) @@ -90,7 +90,7 @@ test "gets an atom feed", %{conn: conn, user: user, object: object, max_id: max_ |> SweetXml.parse() |> SweetXml.xpath(~x"//entry/title/text()"l) - assert activity_titles == ['This & t...'] + assert activity_titles == ['2hu & as'] end test "gets a rss feed", %{conn: conn, user: user, object: object, max_id: max_id} do @@ -105,7 +105,7 @@ test "gets a rss feed", %{conn: conn, user: user, object: object, max_id: max_id |> SweetXml.parse() |> SweetXml.xpath(~x"//item/title/text()"l) - assert activity_titles == ['42 & Thi...', 'This & t...'] + assert activity_titles == ['2hu', '2hu & as'] assert resp =~ FeedView.escape(object.data["content"]) assert resp =~ FeedView.escape(object.data["summary"]) assert resp =~ FeedView.escape(object.data["context"]) @@ -121,7 +121,7 @@ test "gets a rss feed", %{conn: conn, user: user, object: object, max_id: max_id |> SweetXml.parse() |> SweetXml.xpath(~x"//item/title/text()"l) - assert activity_titles == ['This & t...'] + assert activity_titles == ['2hu & as'] end test "returns 404 for a missing feed", %{conn: conn} do From 96cfc9575c4c1b10402a40195e1ab4b9b20def26 Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Tue, 15 Nov 2022 23:45:23 +0500 Subject: [PATCH 08/10] document rss/atom fix in changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3cff84a1..0e34fcae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fixed lowercase HTTP HEAD method in the Media Proxy Preview code - Removed useless notification call on Delete activities - Improved performance for filtering out deactivated and invisible users +- RSS and Atom feeds for users work again ### Removed - Quack, the logging backend that pushes to Slack channels From fce29984813b05e827b720656989b27bd6322ddf Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Wed, 16 Nov 2022 00:44:28 +0500 Subject: [PATCH 09/10] use to_rfc2822 instead of pub_date in tests, too --- test/pleroma/web/feed/tag_controller_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/pleroma/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs index a67acdb47..58ab8f137 100644 --- a/test/pleroma/web/feed/tag_controller_test.exs +++ b/test/pleroma/web/feed/tag_controller_test.exs @@ -137,8 +137,8 @@ test "gets a feed (RSS)", %{conn: conn} do ] assert xpath(xml, ~x"//channel/item/pubDate/text()"sl) == [ - FeedView.pub_date(activity2.data["published"]), - FeedView.pub_date(activity1.data["published"]) + FeedView.to_rfc2822(activity2.data["published"]), + FeedView.to_rfc2822(activity1.data["published"]) ] assert xpath(xml, ~x"//channel/item/enclosure/@url"sl) == [ From 72d4d1b392c7889e13b238a190a437090c360f2e Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 19 Dec 2022 14:40:08 -0500 Subject: [PATCH 10/10] Fix TwitterCard meta tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TwitterCard meta tags are supposed to use the attributes "name" and "content". OpenGraph tags use the attributes "property" and "content". Twitter itself is smart enough to detect broken meta tags and discover the TwitterCard using "property" and "content", but other platforms that only implement parsing of TwitterCards and not OpenGraph may fail to correctly detect the tags as they're under the wrong attributes. > "Open Graph protocol also specifies the use of property and content attributes for markup while > Twitter cards use name and content. Twitter’s parser will fall back to using property and content, > so there is no need to modify existing Open Graph protocol markup if it already exists." [0] [0] https://developer.twitter.com/en/docs/twitter-for-websites/cards/guides/getting-started --- CHANGELOG.md | 1 + .../web/metadata/providers/twitter_card.ex | 43 +++++++------- .../metadata/providers/twitter_card_test.exs | 56 +++++++++---------- 3 files changed, 50 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3cff84a1..3d42a4cfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fixed lowercase HTTP HEAD method in the Media Proxy Preview code - Removed useless notification call on Delete activities - Improved performance for filtering out deactivated and invisible users +- TwitterCard meta tags conformance ### Removed - Quack, the logging backend that pushes to Slack channels diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex index bf0a12212..2dac22ee2 100644 --- a/lib/pleroma/web/metadata/providers/twitter_card.ex +++ b/lib/pleroma/web/metadata/providers/twitter_card.ex @@ -20,12 +20,12 @@ def build_tags(%{activity_id: id, object: object, user: user}) do [ title_tag(user), - {:meta, [property: "twitter:description", content: scrubbed_content], []} + {:meta, [name: "twitter:description", content: scrubbed_content], []} ] ++ if attachments == [] or Metadata.activity_nsfw?(object) do [ image_tag(user), - {:meta, [property: "twitter:card", content: "summary"], []} + {:meta, [name: "twitter:card", content: "summary"], []} ] else attachments @@ -37,20 +37,19 @@ def build_tags(%{user: user}) do with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do [ title_tag(user), - {:meta, [property: "twitter:description", content: truncated_bio], []}, + {:meta, [name: "twitter:description", content: truncated_bio], []}, image_tag(user), - {:meta, [property: "twitter:card", content: "summary"], []} + {:meta, [name: "twitter:card", content: "summary"], []} ] end end defp title_tag(user) do - {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []} + {:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []} end def image_tag(user) do - {:meta, [property: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))], - []} + {:meta, [name: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))], []} end defp build_attachments(id, %{data: %{"attachment" => attachments}}) do @@ -60,10 +59,10 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do case Utils.fetch_media_type(@media_types, url["mediaType"]) do "audio" -> [ - {:meta, [property: "twitter:card", content: "player"], []}, - {:meta, [property: "twitter:player:width", content: "480"], []}, - {:meta, [property: "twitter:player:height", content: "80"], []}, - {:meta, [property: "twitter:player", content: player_url(id)], []} + {:meta, [name: "twitter:card", content: "player"], []}, + {:meta, [name: "twitter:player:width", content: "480"], []}, + {:meta, [name: "twitter:player:height", content: "80"], []}, + {:meta, [name: "twitter:player", content: player_url(id)], []} | acc ] @@ -74,10 +73,10 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do # workaround. "image" -> [ - {:meta, [property: "twitter:card", content: "summary_large_image"], []}, + {:meta, [name: "twitter:card", content: "summary_large_image"], []}, {:meta, [ - property: "twitter:player", + name: "twitter:player", content: MediaProxy.url(url["href"]) ], []} | acc @@ -90,14 +89,14 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do width = url["width"] || 480 [ - {:meta, [property: "twitter:card", content: "player"], []}, - {:meta, [property: "twitter:player", content: player_url(id)], []}, - {:meta, [property: "twitter:player:width", content: "#{width}"], []}, - {:meta, [property: "twitter:player:height", content: "#{height}"], []}, - {:meta, [property: "twitter:player:stream", content: MediaProxy.url(url["href"])], + {:meta, [name: "twitter:card", content: "player"], []}, + {:meta, [name: "twitter:player", content: player_url(id)], []}, + {:meta, [name: "twitter:player:width", content: "#{width}"], []}, + {:meta, [name: "twitter:player:height", content: "#{height}"], []}, + {:meta, [name: "twitter:player:stream", content: MediaProxy.url(url["href"])], []}, - {:meta, - [property: "twitter:player:stream:content_type", content: url["mediaType"]], []} + {:meta, [name: "twitter:player:stream:content_type", content: url["mediaType"]], + []} | acc ] @@ -123,8 +122,8 @@ defp maybe_add_dimensions(metadata, url) do !is_nil(url["height"]) && !is_nil(url["width"]) -> metadata ++ [ - {:meta, [property: "twitter:player:width", content: "#{url["width"]}"], []}, - {:meta, [property: "twitter:player:height", content: "#{url["height"]}"], []} + {:meta, [name: "twitter:player:width", content: "#{url["width"]}"], []}, + {:meta, [name: "twitter:player:height", content: "#{url["height"]}"], []} ] true -> diff --git a/test/pleroma/web/metadata/providers/twitter_card_test.exs b/test/pleroma/web/metadata/providers/twitter_card_test.exs index 1a0cea9ce..be4cfbe7b 100644 --- a/test/pleroma/web/metadata/providers/twitter_card_test.exs +++ b/test/pleroma/web/metadata/providers/twitter_card_test.exs @@ -22,10 +22,10 @@ test "it renders twitter card for user info" do res = TwitterCard.build_tags(%{user: user}) assert res == [ - {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}, - {:meta, [property: "twitter:description", content: "born 19 March 1994"], []}, - {:meta, [property: "twitter:image", content: avatar_url], []}, - {:meta, [property: "twitter:card", content: "summary"], []} + {:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []}, + {:meta, [name: "twitter:description", content: "born 19 March 1994"], []}, + {:meta, [name: "twitter:image", content: avatar_url], []}, + {:meta, [name: "twitter:card", content: "summary"], []} ] end @@ -47,11 +47,11 @@ test "it uses summary twittercard if post has no attachment" do result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id}) assert [ - {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}, - {:meta, [property: "twitter:description", content: "pleroma in a nutshell"], []}, - {:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"], + {:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []}, + {:meta, [name: "twitter:description", content: "pleroma in a nutshell"], []}, + {:meta, [name: "twitter:image", content: "http://localhost:4001/images/avi.png"], []}, - {:meta, [property: "twitter:card", content: "summary"], []} + {:meta, [name: "twitter:card", content: "summary"], []} ] == result end @@ -73,15 +73,15 @@ test "it uses summary as description if post has one" do result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id}) assert [ - {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}, + {:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []}, {:meta, [ - property: "twitter:description", + name: "twitter:description", content: "Public service announcement on caffeine consumption" ], []}, - {:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"], + {:meta, [name: "twitter:image", content: "http://localhost:4001/images/avi.png"], []}, - {:meta, [property: "twitter:card", content: "summary"], []} + {:meta, [name: "twitter:card", content: "summary"], []} ] == result end @@ -123,11 +123,11 @@ test "it renders avatar not attachment if post is nsfw and unfurl_nsfw is disabl result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id}) assert [ - {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}, - {:meta, [property: "twitter:description", content: "pleroma in a nutshell"], []}, - {:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"], + {:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []}, + {:meta, [name: "twitter:description", content: "pleroma in a nutshell"], []}, + {:meta, [name: "twitter:image", content: "http://localhost:4001/images/avi.png"], []}, - {:meta, [property: "twitter:card", content: "summary"], []} + {:meta, [name: "twitter:card", content: "summary"], []} ] == result end @@ -179,26 +179,26 @@ test "it renders supported types of attachments and skips unknown types" do result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id}) assert [ - {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}, - {:meta, [property: "twitter:description", content: "pleroma in a nutshell"], []}, - {:meta, [property: "twitter:card", content: "summary_large_image"], []}, - {:meta, [property: "twitter:player", content: "https://pleroma.gov/tenshi.png"], []}, - {:meta, [property: "twitter:player:width", content: "1280"], []}, - {:meta, [property: "twitter:player:height", content: "1024"], []}, - {:meta, [property: "twitter:card", content: "player"], []}, + {:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []}, + {:meta, [name: "twitter:description", content: "pleroma in a nutshell"], []}, + {:meta, [name: "twitter:card", content: "summary_large_image"], []}, + {:meta, [name: "twitter:player", content: "https://pleroma.gov/tenshi.png"], []}, + {:meta, [name: "twitter:player:width", content: "1280"], []}, + {:meta, [name: "twitter:player:height", content: "1024"], []}, + {:meta, [name: "twitter:card", content: "player"], []}, {:meta, [ - property: "twitter:player", + name: "twitter:player", content: Router.Helpers.o_status_url(Endpoint, :notice_player, activity.id) ], []}, - {:meta, [property: "twitter:player:width", content: "800"], []}, - {:meta, [property: "twitter:player:height", content: "600"], []}, + {:meta, [name: "twitter:player:width", content: "800"], []}, + {:meta, [name: "twitter:player:height", content: "600"], []}, {:meta, [ - property: "twitter:player:stream", + name: "twitter:player:stream", content: "https://pleroma.gov/about/juche.webm" ], []}, - {:meta, [property: "twitter:player:stream:content_type", content: "video/webm"], []} + {:meta, [name: "twitter:player:stream:content_type", content: "video/webm"], []} ] == result end end