aboutsummaryrefslogtreecommitdiff
path: root/lib/pleroma/web/feed
diff options
context:
space:
mode:
authordcc <dcc@logografos.com>2023-09-02 00:52:52 -0700
committerdcc <dcc@logografos.com>2023-09-02 00:52:52 -0700
commit3a4773c3c2bd0bbef244eb519b07208da9108e49 (patch)
tree973567a6f3abb37bfb0f785b1cad14ed55840ef5 /lib/pleroma/web/feed
downloadanni-3a4773c3c2bd0bbef244eb519b07208da9108e49.tar.gz
anni-3a4773c3c2bd0bbef244eb519b07208da9108e49.tar.bz2
anni-3a4773c3c2bd0bbef244eb519b07208da9108e49.zip
First
Diffstat (limited to 'lib/pleroma/web/feed')
-rw-r--r--lib/pleroma/web/feed/feed_view.ex190
-rw-r--r--lib/pleroma/web/feed/tag_controller.ex48
-rw-r--r--lib/pleroma/web/feed/user_controller.ex80
3 files changed, 318 insertions, 0 deletions
diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex
new file mode 100644
index 0000000..034722e
--- /dev/null
+++ b/lib/pleroma/web/feed/feed_view.ex
@@ -0,0 +1,190 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Feed.FeedView do
+ use Phoenix.HTML
+ use Pleroma.Web, :view
+
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.Gettext
+ alias Pleroma.Web.MediaProxy
+
+ 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)
+
+ def prepare_activity(activity, opts \\ []) do
+ object = Object.normalize(activity, fetch: false)
+
+ actor =
+ if opts[:actor] do
+ Pleroma.User.get_cached_by_ap_id(activity.actor)
+ end
+
+ %{
+ activity: activity,
+ object: object,
+ data: Map.get(object, :data),
+ actor: actor
+ }
+ end
+
+ def most_recent_update(activities) do
+ with %{updated_at: updated_at} <- List.first(activities) do
+ to_rfc3339(updated_at)
+ end
+ end
+
+ def most_recent_update(activities, user, :atom) do
+ (List.first(activities) || user).updated_at
+ |> to_rfc3339()
+ end
+
+ def most_recent_update(activities, user, :rss) do
+ (List.first(activities) || user).updated_at
+ |> to_rfc2822()
+ end
+
+ def feed_logo do
+ case Pleroma.Config.get([:feed, :logo]) do
+ nil ->
+ "#{Pleroma.Web.Endpoint.url()}/static/logo.svg"
+
+ logo ->
+ "#{Pleroma.Web.Endpoint.url()}#{logo}"
+ end
+ |> MediaProxy.url()
+ end
+
+ def email(user) do
+ user.nickname <> "@" <> Pleroma.Web.Endpoint.host()
+ end
+
+ def logo(user) do
+ user
+ |> User.avatar_url()
+ |> MediaProxy.url()
+ end
+
+ def last_activity(activities), do: List.last(activities)
+
+ def activity_title(%{"content" => content} = data, opts \\ %{}) do
+ summary = Map.get(data, "summary", "")
+
+ title =
+ cond do
+ summary != "" -> summary
+ content != "" -> activity_content(data)
+ true -> "a post"
+ end
+
+ title
+ |> Pleroma.Web.Metadata.Utils.scrub_html_and_truncate(opts[:max_length], opts[:omission])
+ |> HtmlEntities.encode()
+ 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]/, "")
+ end
+
+ def activity_content(_), do: ""
+
+ def activity_context(activity), do: escape(activity.data["context"])
+
+ def attachment_href(attachment) do
+ attachment["url"]
+ |> hd()
+ |> Map.get("href")
+ end
+
+ def attachment_type(attachment) do
+ attachment["url"]
+ |> hd()
+ |> Map.get("mediaType")
+ end
+
+ def get_href(id) do
+ with %Object{data: %{"external_url" => external_url}} <- Object.get_cached_by_ap_id(id) do
+ external_url
+ else
+ _e -> id
+ end
+ end
+
+ def escape(html) do
+ html
+ |> 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_rfc2822(String.t() | DateTime.t() | NativeDateTime.t()) :: String.t()
+ def to_rfc2822(datestr) when is_binary(datestr) do
+ datestr
+ |> Timex.parse!("{ISO:Extended}")
+ |> to_rfc2822()
+ end
+
+ def to_rfc2822(%DateTime{} = date) do
+ date
+ |> DateTime.to_naive()
+ |> NaiveDateTime.to_erl()
+ |> rfc2822_from_erl()
+ end
+
+ def to_rfc2822(nd) do
+ nd
+ |> Timex.to_datetime()
+ |> 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/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex
new file mode 100644
index 0000000..e607673
--- /dev/null
+++ b/lib/pleroma/web/feed/tag_controller.ex
@@ -0,0 +1,48 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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
+
+ def feed(conn, params) do
+ if Config.get!([:instance, :public]) do
+ render_feed(conn, params)
+ else
+ render_error(conn, :not_found, "Not found")
+ end
+ end
+
+ defp render_feed(conn, %{"tag" => raw_tag} = params) do
+ {format, tag} = parse_tag(raw_tag)
+
+ activities =
+ %{type: ["Create"], tag: tag}
+ |> Pleroma.Maps.put_if_present(:max_id, params["max_id"])
+ |> ActivityPub.fetch_public_activities()
+
+ conn
+ |> put_resp_content_type("application/#{format}+xml")
+ |> put_view(FeedView)
+ |> render("tag.#{format}",
+ activities: activities,
+ tag: tag,
+ feed_config: Config.get([:feed])
+ )
+ end
+
+ @spec parse_tag(binary() | any()) :: {format :: String.t(), tag :: String.t()}
+ defp parse_tag(raw_tag) do
+ case is_binary(raw_tag) && Enum.reverse(String.split(raw_tag, ".")) do
+ [format | tag] when format in ["rss", "atom"] ->
+ {format, Enum.join(tag, ".")}
+
+ _ ->
+ {"atom", raw_tag}
+ end
+ end
+end
diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex
new file mode 100644
index 0000000..6657c2b
--- /dev/null
+++ b/lib/pleroma/web/feed/user_controller.ex
@@ -0,0 +1,80 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Feed.UserController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Config
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.ActivityPubController
+ alias Pleroma.Web.Feed.FeedView
+
+ plug(Pleroma.Web.Plugs.SetFormatPlug when action in [:feed_redirect])
+
+ action_fallback(:errors)
+
+ def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
+ with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
+ Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
+ else
+ _ -> Pleroma.Web.Fallback.RedirectController.redirector(conn, nil)
+ end
+ end
+
+ def feed_redirect(%{assigns: %{format: format}} = conn, _params)
+ when format in ["json", "activity+json"] do
+ ActivityPubController.call(conn, :user)
+ end
+
+ def feed_redirect(conn, %{"nickname" => nickname}) do
+ with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
+ redirect(conn, external: "#{Routes.user_feed_url(conn, :feed, user.nickname)}.atom")
+ end
+ end
+
+ def feed(conn, %{"nickname" => nickname} = params) do
+ format = get_format(conn)
+
+ format =
+ if format in ["atom", "rss"] do
+ format
+ else
+ "atom"
+ end
+
+ with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)},
+ {_, :visible} <- {:visibility, User.visible_for(user, _reading_user = nil)} do
+ activities =
+ %{
+ type: ["Create"],
+ actor_id: user.ap_id
+ }
+ |> Pleroma.Maps.put_if_present(:max_id, params["max_id"])
+ |> ActivityPub.fetch_public_or_unlisted_activities()
+
+ conn
+ |> put_resp_content_type("application/#{format}+xml")
+ |> put_view(FeedView)
+ |> render("user.#{format}",
+ user: user,
+ activities: activities,
+ feed_config: Config.get([:feed])
+ )
+ end
+ end
+
+ def errors(conn, {:error, :not_found}) do
+ render_error(conn, :not_found, "Not found")
+ end
+
+ def errors(conn, {:fetch_user, %User{local: false}}), do: errors(conn, {:error, :not_found})
+ def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
+
+ def errors(conn, {:visibility, _}), do: errors(conn, {:error, :not_found})
+
+ def errors(conn, _) do
+ render_error(conn, :internal_server_error, "Something went wrong")
+ end
+end