add tag feeds
authorMaksim Pechnikov <parallel588@gmail.com>
Tue, 17 Dec 2019 19:13:45 +0000 (22:13 +0300)
committerMaksim Pechnikov <parallel588@gmail.com>
Wed, 18 Dec 2019 06:19:37 +0000 (09:19 +0300)
CHANGELOG.md
lib/pleroma/web/controller_helper.ex
lib/pleroma/web/feed/feed_view.ex
lib/pleroma/web/feed/tag_controller.ex
lib/pleroma/web/feed/user_controller.ex
lib/pleroma/web/metadata/utils.ex
lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex [new file with mode: 0644]
lib/pleroma/web/templates/feed/feed/tag.xml.eex
test/web/feed/tag_controller_test.exs

index c133cd9ec4f3335f9132c025f5822bb457ffe7c7..feb3f9b3c68a0a47a8d15d17cd57950a539ab959 100644 (file)
@@ -86,6 +86,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: `/api/v1/update_credentials` accepts `actor_type` field.
 - Captcha: Support native provider
 - Captcha: Enable by default
+- Configuration: `feed.logo` option for tag feed.
+- Tag feed: `/tags/:tag.rss` - list public statuses by hashtag.
 </details>
 
 ### Fixed
index 9a4e322c920e24b7a7e655c45177fac53cb5cbc8..e3d7a465b8efbed81edf5dfec65c2428cf01692e 100644 (file)
@@ -76,8 +76,7 @@ defmodule Pleroma.Web.ControllerHelper do
     end
   end
 
-  def try_render(conn, target, params)
-      when is_binary(target) do
+  def try_render(conn, target, params) when is_binary(target) do
     case render(conn, target, params) do
       nil -> render_error(conn, :not_implemented, "Can't display this activity")
       res -> res
@@ -87,4 +86,8 @@ defmodule Pleroma.Web.ControllerHelper do
   def try_render(conn, _, _) do
     render_error(conn, :not_implemented, "Can't display this activity")
   end
+
+  @spec put_in_if_exist(map(), atom() | String.t(), any) :: map()
+  def put_in_if_exist(map, _key, nil), do: map
+  def put_in_if_exist(map, key, value), do: put_in(map, key, value)
 end
index bb1332fd306be715e2bb8f1073f833a0320f5de0..2e7db1ebbd3966ac4cee2ca1bbd0b8ef813683b1 100644 (file)
@@ -13,6 +13,15 @@ defmodule Pleroma.Web.Feed.FeedView do
 
   require Pleroma.Constants
 
+  @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: Timex.format!(date, "{RFC822}")
+
   def prepare_activity(activity) do
     object = activity_object(activity)
 
@@ -28,6 +37,17 @@ defmodule Pleroma.Web.Feed.FeedView do
     |> NaiveDateTime.to_iso8601()
   end
 
+  def feed_logo do
+    case Pleroma.Config.get([:feed, :logo]) do
+      nil ->
+        "#{Pleroma.Web.base_url()}/static/logo.png"
+
+      logo ->
+        "#{Pleroma.Web.base_url()}#{logo}"
+    end
+    |> MediaProxy.url()
+  end
+
   def logo(user) do
     user
     |> User.avatar_url()
@@ -40,6 +60,8 @@ defmodule Pleroma.Web.Feed.FeedView do
 
   def activity_title(%{data: %{"content" => content}}, opts \\ %{}) do
     content
+    |> Pleroma.Web.Metadata.Utils.scrub_html()
+    |> Pleroma.Emoji.Formatter.demojify()
     |> Formatter.truncate(opts[:max_length], opts[:omission])
     |> escape()
   end
@@ -50,6 +72,8 @@ defmodule Pleroma.Web.Feed.FeedView do
     |> escape()
   end
 
+  def activity_content(_), do: ""
+
   def activity_context(activity), do: activity.data["context"]
 
   def attachment_href(attachment) do
index 66abc186b82a3a26922fe8e56ab46e9e31822077..97ce147de9845ca00ac1e189b3b703883b17393e 100644 (file)
@@ -9,20 +9,24 @@ defmodule Pleroma.Web.Feed.TagController do
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.Feed.FeedView
 
-  def feed(conn, %{"tag" => tag} = params) do
+  import Pleroma.Web.ControllerHelper, only: [put_in_if_exist: 3]
+
+  def feed(conn, %{"tag" => raw_tag} = params) do
+    tag = parse_tag(raw_tag)
+
     activities =
-      %{
-        "type" => ["Create"],
-        "whole_db" => true,
-        "tag" => parse_tag(tag)
-      }
-      |> Map.merge(Map.take(params, ["max_id"]))
+      %{"type" => ["Create"], "whole_db" => true, "tag" => tag}
+      |> put_in_if_exist("max_id", params["max_id"])
       |> ActivityPub.fetch_public_activities()
 
     conn
     |> put_resp_content_type("application/atom+xml")
     |> put_view(FeedView)
-    |> render("tag.xml", activities: activities, feed_config: Config.get([:feed]))
+    |> render("tag.xml",
+      activities: activities,
+      tag: tag,
+      feed_config: Config.get([:feed])
+    )
   end
 
   defp parse_tag(raw_tag) when is_binary(raw_tag) do
index e5d8427ce41abdd11b7a0a43ddd9473e9132bfbd..cf04fd49725ab65c9aa5ccf18ed1d99b0be85449 100644 (file)
@@ -11,6 +11,8 @@ defmodule Pleroma.Web.Feed.UserController do
   alias Pleroma.Web.ActivityPub.ActivityPubController
   alias Pleroma.Web.Feed.FeedView
 
+  import Pleroma.Web.ControllerHelper, only: [put_in_if_exist: 3]
+
   plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect])
 
   action_fallback(:errors)
@@ -35,12 +37,8 @@ defmodule Pleroma.Web.Feed.UserController do
   def feed(conn, %{"nickname" => nickname} = params) do
     with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
       activities =
-        %{
-          "type" => ["Create"],
-          "whole_db" => true,
-          "actor_id" => user.ap_id
-        }
-        |> Map.merge(Map.take(params, ["max_id"]))
+        %{"type" => ["Create"], "whole_db" => true, "actor_id" => user.ap_id}
+        |> put_in_if_exist("max_id", params["max_id"])
         |> ActivityPub.fetch_public_activities()
 
       conn
index 382ecf426cdcea8c7675422827bca4051be2dd00..c0dae1b7eb8e077e3b7035721f47dc60b1ade782 100644 (file)
@@ -19,15 +19,22 @@ defmodule Pleroma.Web.Metadata.Utils do
   end
 
   def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do
+    content
+    |> scrub_html
+    |> Emoji.Formatter.demojify()
+    |> Formatter.truncate(max_length)
+  end
+
+  def scrub_html(content) when is_binary(content) do
     content
     # html content comes from DB already encoded, decode first and scrub after
     |> HtmlEntities.decode()
     |> String.replace(~r/<br\s?\/?>/, " ")
     |> HTML.strip_tags()
-    |> Emoji.Formatter.demojify()
-    |> Formatter.truncate(max_length)
   end
 
+  def scrub_html(content), do: content
+
   def attachment_url(url) do
     MediaProxy.url(url)
   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
new file mode 100644 (file)
index 0000000..295574d
--- /dev/null
@@ -0,0 +1,15 @@
+<item>
+  <title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title>
+  
+  
+  <guid isPermalink="true"><%= activity_context(@activity) %></guid>
+  <link><%= activity_context(@activity) %></link>
+  <pubDate><%= pub_date(@data["published"]) %></pubDate>
+  
+  <description><%= activity_content(@object) %></description>
+  <%= for attachment <- @data["attachment"] || [] do %>
+    <enclosure url="<%= attachment_href(attachment) %>" type="<%= attachment_type(attachment) %>"/>
+  <% end %>
+  
+</item>
+
index 52b1d7b7d2bf214c6b30ff3243ea88d77bfe39e0..eeda01a0468bb60951402bec9637c7264ef24e46 100644 (file)
@@ -1,10 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<feed
-  xmlns="http://www.w3.org/2005/Atom"
-  xmlns:thr="http://purl.org/syndication/thread/1.0"
-  xmlns:activity="http://activitystrea.ms/spec/1.0/"
-  xmlns:poco="http://portablecontacts.net/spec/1.0"
-  xmlns:ostatus="http://ostatus.org/schema/1.0">
+<rss version="2.0" xmlns:webfeeds="http://webfeeds.org/rss/1.0">
+  <channel>
 
-  <title>TAGS</title>
-</feed>
+
+    <title>#<%= @tag %></title>
+    <description>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</description>
+    <link><%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %></link>
+    <webfeeds:logo><%= feed_logo() %></webfeeds:logo>
+    <webfeeds:accentColor>2b90d9</webfeeds:accentColor>
+    <%= for activity <- @activities do %>
+    <%= render @view_module, "_tag_activity.xml", Map.merge(assigns, prepare_activity(activity)) %>
+    <% end %>
+  </channel>
+</rss>
index 82115f811cd4417bf1550f29e79611f694a165a1..e9b58c8cd07c7607cbc97081de188256d4dc07f2 100644 (file)
@@ -6,26 +6,84 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
   use Pleroma.Web.ConnCase
 
   import Pleroma.Factory
+  import SweetXml
+
+  alias Pleroma.Web.Feed.FeedView
 
   clear_config([:feed])
 
   test "gets a feed", %{conn: conn} do
     Pleroma.Config.put(
       [:feed, :post_title],
-      %{max_length: 10, omission: "..."}
+      %{max_length: 25, omission: "..."}
     )
 
     user = insert(:user)
-    {:ok, _activity1} = Pleroma.Web.CommonAPI.post(user, %{"status" => "yeah #PleromaArt"})
+    {:ok, activity1} = Pleroma.Web.CommonAPI.post(user, %{"status" => "yeah #PleromaArt"})
+
+    object = Pleroma.Object.normalize(activity1)
+
+    object_data =
+      Map.put(object.data, "attachment", [
+        %{
+          "url" => [
+            %{
+              "href" =>
+                "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
+              "mediaType" => "video/mp4",
+              "type" => "Link"
+            }
+          ]
+        }
+      ])
+
+    object
+    |> Ecto.Changeset.change(data: object_data)
+    |> Pleroma.Repo.update()
 
-    {:ok, _activity2} =
+    {:ok, activity2} =
       Pleroma.Web.CommonAPI.post(user, %{"status" => "42 This is :moominmamma #PleromaArt"})
 
     {:ok, _activity3} = Pleroma.Web.CommonAPI.post(user, %{"status" => "This is :moominmamma"})
 
-    assert conn
-           |> put_req_header("content-type", "application/atom+xml")
-           |> get("/tags/pleromaart.rss")
-           |> response(200)
+    response =
+      conn
+      |> put_req_header("content-type", "application/atom+xml")
+      |> get("/tags/pleromaart.rss")
+      |> response(200)
+
+    xml = parse(response)
+    assert xpath(xml, ~x"//channel/title/text()") == '#pleromaart'
+
+    assert xpath(xml, ~x"//channel/description/text()"s) ==
+             "These are public toots tagged with #pleromaart. You can interact with them if you have an account anywhere in the fediverse."
+
+    assert xpath(xml, ~x"//channel/link/text()") ==
+             '#{Pleroma.Web.base_url()}/tags/pleromaart.rss'
+
+    assert xpath(xml, ~x"//channel/webfeeds:logo/text()") ==
+             '#{Pleroma.Web.base_url()}/static/logo.png'
+
+    assert xpath(xml, ~x"//channel/item/title/text()"l) == [
+             '42 This is :moominmamm...',
+             'yeah #PleromaArt'
+           ]
+
+    assert xpath(xml, ~x"//channel/item/pubDate/text()"sl) == [
+             FeedView.pub_date(activity1.data["published"]),
+             FeedView.pub_date(activity2.data["published"])
+           ]
+
+    assert xpath(xml, ~x"//channel/item/enclosure/@url"sl) == [
+             "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4"
+           ]
+
+    obj1 = Pleroma.Object.normalize(activity1)
+    obj2 = Pleroma.Object.normalize(activity2)
+
+    assert xpath(xml, ~x"//channel/item/description/text()"sl) == [
+             HtmlEntities.decode(FeedView.activity_content(obj2)),
+             HtmlEntities.decode(FeedView.activity_content(obj1))
+           ]
   end
 end