Merge branch 'develop' into feature/tag_feed
authorMaksim Pechnikov <parallel588@gmail.com>
Thu, 23 Jan 2020 08:42:10 +0000 (11:42 +0300)
committerMaksim Pechnikov <parallel588@gmail.com>
Thu, 23 Jan 2020 08:42:10 +0000 (11:42 +0300)
16 files changed:
CHANGELOG.md
lib/pleroma/emails/admin_email.ex
lib/pleroma/web/controller_helper.ex
lib/pleroma/web/feed/feed_view.ex
lib/pleroma/web/feed/tag_controller.ex [new file with mode: 0644]
lib/pleroma/web/feed/user_controller.ex [moved from lib/pleroma/web/feed/feed_controller.ex with 80% similarity]
lib/pleroma/web/metadata/feed.ex
lib/pleroma/web/metadata/utils.ex
lib/pleroma/web/router.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 [new file with mode: 0644]
lib/pleroma/web/templates/feed/feed/user.xml.eex [moved from lib/pleroma/web/templates/feed/feed/feed.xml.eex with 67% similarity]
test/emails/admin_email_test.exs
test/user_test.exs
test/web/feed/tag_controller_test.exs [new file with mode: 0644]
test/web/feed/user_controller_test.exs [moved from test/web/feed/feed_controller_test.exs with 97% similarity]

index 3f691463894dee8c889cd96fbb8b329a70df96a9..14952ffc426c998b1dc34b64eba726f429c5f4c2 100644 (file)
@@ -97,6 +97,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: Add `emoji_reactions` property to Statuses
 - Mastodon API: Change emoji reaction reply format
 - Notifications: Added `pleroma:emoji_reaction` notification type
+- Configuration: `feed.logo` option for tag feed.
+- Tag feed: `/tags/:tag.rss` - list public statuses by hashtag.
 </details>
 
 ### Fixed
index b15e4041bd4295d17d3a6a5124748ff87c60778a..d7dd4b2e064908ccd91cd937f4a784a2afb33600 100644 (file)
@@ -17,7 +17,7 @@ defmodule Pleroma.Emails.AdminEmail do
   end
 
   defp user_url(user) do
-    Helpers.feed_url(Pleroma.Web.Endpoint, :feed_redirect, user.id)
+    Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, user.id)
   end
 
   def report(to, reporter, account, statuses, comment) do
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
diff --git a/lib/pleroma/web/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex
new file mode 100644 (file)
index 0000000..97ce147
--- /dev/null
@@ -0,0 +1,40 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Feed.TagController do
+  use Pleroma.Web, :controller
+
+  alias Pleroma.Config
+  alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.Feed.FeedView
+
+  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" => 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,
+      tag: tag,
+      feed_config: Config.get([:feed])
+    )
+  end
+
+  defp parse_tag(raw_tag) when is_binary(raw_tag) do
+    case Enum.reverse(String.split(raw_tag, ".")) do
+      [format | tag] when format in ["atom", "rss"] -> Enum.join(tag, ".")
+      _ -> raw_tag
+    end
+  end
+
+  defp parse_tag(raw_tag), do: raw_tag
+end
similarity index 80%
rename from lib/pleroma/web/feed/feed_controller.ex
rename to lib/pleroma/web/feed/user_controller.ex
index d0e23007df7610544587136b0a86a69aa130f6f3..cf04fd49725ab65c9aa5ccf18ed1d99b0be85449 100644 (file)
@@ -2,13 +2,16 @@
 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.Feed.FeedController do
+defmodule Pleroma.Web.Feed.UserController do
   use Pleroma.Web, :controller
 
   alias Fallback.RedirectController
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   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])
 
@@ -27,24 +30,21 @@ defmodule Pleroma.Web.Feed.FeedController do
 
   def feed_redirect(conn, %{"nickname" => nickname}) do
     with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
-      redirect(conn, external: "#{feed_url(conn, :feed, user.nickname)}.atom")
+      redirect(conn, external: "#{user_feed_url(conn, :feed, user.nickname)}.atom")
     end
   end
 
   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
       |> put_resp_content_type("application/atom+xml")
-      |> render("feed.xml",
+      |> put_view(FeedView)
+      |> render("user.xml",
         user: user,
         activities: activities,
         feed_config: Pleroma.Config.get([:feed])
index 8043e6c547f52a63c33193ea24b30582395ea149..ee48913a70bfb65c1a1cfa2803a5b9dca1fc52bd 100644 (file)
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.Metadata.Providers.Feed do
        [
          rel: "alternate",
          type: "application/atom+xml",
-         href: Helpers.feed_path(Endpoint, :feed, user.nickname) <> ".atom"
+         href: Helpers.user_feed_path(Endpoint, :feed, user.nickname) <> ".atom"
        ], []}
     ]
   end
index 589d11901a0ceb91f50d5da672a909805c0c25ac..000bd9f66f5559a79c525596d9ebf9b4d6338ba1 100644 (file)
@@ -20,16 +20,23 @@ 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()
+    |> HtmlEntities.decode()
+    |> 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()
-    |> HtmlEntities.decode()
-    |> Formatter.truncate(max_length)
   end
 
+  def scrub_html(content), do: content
+
   def attachment_url(url) do
     MediaProxy.url(url)
   end
index ef6e5a56514fa7ba00cd9ec2f8aa7e279c34bc07..b5c1d85c700a4a4843cf358173d1c108b409f024 100644 (file)
@@ -527,8 +527,10 @@ defmodule Pleroma.Web.Router do
     get("/notice/:id", OStatus.OStatusController, :notice)
     get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
 
-    get("/users/:nickname/feed", Feed.FeedController, :feed)
-    get("/users/:nickname", Feed.FeedController, :feed_redirect)
+    get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
+    get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
+
+    get("/tags/:tag", Feed.TagController, :feed, as: :tag_feed)
   end
 
   scope "/", Pleroma.Web do
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>
+
diff --git a/lib/pleroma/web/templates/feed/feed/tag.xml.eex b/lib/pleroma/web/templates/feed/feed/tag.xml.eex
new file mode 100644 (file)
index 0000000..eeda01a
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rss version="2.0" xmlns:webfeeds="http://webfeeds.org/rss/1.0">
+  <channel>
+
+
+    <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>
similarity index 67%
rename from lib/pleroma/web/templates/feed/feed/feed.xml.eex
rename to lib/pleroma/web/templates/feed/feed/user.xml.eex
index 5ae36d345d79bc88e663eeeab60d3dc634fc9082..d274c08ae06d1b5082e2ff3bb3ad6215b3400ac4 100644 (file)
@@ -6,16 +6,16 @@
   xmlns:poco="http://portablecontacts.net/spec/1.0"
   xmlns:ostatus="http://ostatus.org/schema/1.0">
 
-  <id><%= feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>
+  <id><%= user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>
   <title><%= @user.nickname <> "'s timeline" %></title>
   <updated><%= most_recent_update(@activities, @user) %></updated>
   <logo><%= logo(@user) %></logo>
-  <link rel="self" href="<%= '#{feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>
+  <link rel="self" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>
 
   <%= render @view_module, "_author.xml", assigns %>
 
   <%= if last_activity(@activities) do %>
-    <link rel="next" href="<%= '#{feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
+    <link rel="next" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
   <% end %>
 
   <%= for activity <- @activities do %>
index ad89f9213b10d54ecb70920eed7a4a7fcf9dfa75..383cc3459285670a7108edfa3e0074e94a4004a6 100644 (file)
@@ -19,8 +19,8 @@ defmodule Pleroma.Emails.AdminEmailTest do
       AdminEmail.report(to_user, reporter, account, [%{name: "Test", id: "12"}], "Test comment")
 
     status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, "12")
-    reporter_url = Helpers.feed_url(Pleroma.Web.Endpoint, :feed_redirect, reporter.id)
-    account_url = Helpers.feed_url(Pleroma.Web.Endpoint, :feed_redirect, account.id)
+    reporter_url = Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, reporter.id)
+    account_url = Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, account.id)
 
     assert res.to == [{to_user.name, to_user.email}]
     assert res.from == {config[:name], config[:notify_email]}
index 9da1e02a955977109bc6726c210afebdd422b84f..190b03645bb96e9a7b3ef9b486227ec22112d0f4 100644 (file)
@@ -585,7 +585,7 @@ defmodule Pleroma.UserTest do
     user = insert(:user)
 
     assert User.ap_id(user) ==
-             Pleroma.Web.Router.Helpers.feed_url(
+             Pleroma.Web.Router.Helpers.user_feed_url(
                Pleroma.Web.Endpoint,
                :feed_redirect,
                user.nickname
@@ -596,7 +596,7 @@ defmodule Pleroma.UserTest do
     user = insert(:user)
 
     assert User.ap_followers(user) ==
-             Pleroma.Web.Router.Helpers.feed_url(
+             Pleroma.Web.Router.Helpers.user_feed_url(
                Pleroma.Web.Endpoint,
                :feed_redirect,
                user.nickname
diff --git a/test/web/feed/tag_controller_test.exs b/test/web/feed/tag_controller_test.exs
new file mode 100644 (file)
index 0000000..a56a187
--- /dev/null
@@ -0,0 +1,101 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+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: 25, omission: "..."}
+    )
+
+    user = insert(:user)
+    {: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} =
+      Pleroma.Web.CommonAPI.post(user, %{"status" => "42 This is :moominmamma #PleromaArt"})
+
+    {:ok, _activity3} = Pleroma.Web.CommonAPI.post(user, %{"status" => "This is :moominmamma"})
+
+    response =
+      conn
+      |> put_req_header("content-type", "application/atom+xml")
+      |> get(tag_feed_path(conn, :feed, "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))
+           ]
+
+    response =
+      conn
+      |> put_req_header("content-type", "application/atom+xml")
+      |> get(tag_feed_path(conn, :feed, "pleromaart"))
+      |> 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."
+  end
+end
similarity index 97%
rename from test/web/feed/feed_controller_test.exs
rename to test/web/feed/user_controller_test.exs
index 6f61acf43b2d4b6303a1f673ca0074d20e650d06..41cc9e07ee24021aed7fdb258c99c9bf6bcff0be 100644 (file)
@@ -2,7 +2,7 @@
 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.Feed.FeedControllerTest do
+defmodule Pleroma.Web.Feed.UserControllerTest do
   use Pleroma.Web.ConnCase
 
   import Pleroma.Factory
@@ -49,7 +49,7 @@ defmodule Pleroma.Web.Feed.FeedControllerTest do
     resp =
       conn
       |> put_req_header("content-type", "application/atom+xml")
-      |> get("/users/#{user.nickname}/feed.atom")
+      |> get(user_feed_path(conn, :feed, user.nickname))
       |> response(200)
 
     activity_titles =
@@ -65,7 +65,7 @@ defmodule Pleroma.Web.Feed.FeedControllerTest do
     conn =
       conn
       |> put_req_header("content-type", "application/atom+xml")
-      |> get("/users/nonexisting/feed.atom")
+      |> get(user_feed_path(conn, :feed, "nonexisting"))
 
     assert response(conn, 404)
   end
@@ -91,7 +91,7 @@ defmodule Pleroma.Web.Feed.FeedControllerTest do
       response =
         conn
         |> put_req_header("accept", "application/xml")
-        |> get("/users/jimm")
+        |> get(user_feed_path(conn, :feed, "jimm"))
         |> response(404)
 
       assert response == ~S({"error":"Not found"})