summaryrefslogtreecommitdiff
path: root/lib/pleroma/web/activity_pub
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pleroma/web/activity_pub')
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex123
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex18
-rw-r--r--lib/pleroma/web/activity_pub/builder.ex97
-rw-r--r--lib/pleroma/web/activity_pub/mrf.ex19
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf/emoji_policy.ex281
-rw-r--r--lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf/force_mention.ex59
-rw-r--r--lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex10
-rw-r--r--lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex77
-rw-r--r--lib/pleroma/web/activity_pub/mrf/keyword_policy.ex21
-rw-r--r--lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex16
-rw-r--r--lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex265
-rw-r--r--lib/pleroma/web/activity_pub/mrf/policy.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex49
-rw-r--r--lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex10
-rw-r--r--lib/pleroma/web/activity_pub/mrf/utils.ex15
-rw-r--r--lib/pleroma/web/activity_pub/object_validator.ex18
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex3
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/announce_validator.ex2
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex1
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex4
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex (renamed from lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex)16
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex5
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_fields.ex6
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_fixes.ex49
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex51
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex4
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/question_validator.ex2
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/tag_validator.ex20
-rw-r--r--lib/pleroma/web/activity_pub/pipeline.ex2
-rw-r--r--lib/pleroma/web/activity_pub/publisher.ex204
-rw-r--r--lib/pleroma/web/activity_pub/relay.ex2
-rw-r--r--lib/pleroma/web/activity_pub/side_effects.ex110
-rw-r--r--lib/pleroma/web/activity_pub/side_effects/handling.ex2
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex85
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex130
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex13
-rw-r--r--lib/pleroma/web/activity_pub/visibility.ex50
40 files changed, 1511 insertions, 340 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 1ab2db94a..5bb0fba6e 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -74,29 +74,40 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp check_remote_limit(_), do: true
def increase_note_count_if_public(actor, object) do
- if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
+ if public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
end
def decrease_note_count_if_public(actor, object) do
- if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
+ if public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
end
def update_last_status_at_if_public(actor, object) do
- if is_public?(object), do: User.update_last_status_at(actor), else: {:ok, actor}
+ if public?(object), do: User.update_last_status_at(actor), else: {:ok, actor}
end
defp increase_replies_count_if_reply(%{
"object" => %{"inReplyTo" => reply_ap_id} = object,
"type" => "Create"
}) do
- if is_public?(object) do
+ if public?(object) do
Object.increase_replies_count(reply_ap_id)
end
end
defp increase_replies_count_if_reply(_create_data), do: :noop
- @object_types ~w[ChatMessage Question Answer Audio Video Event Article Note Page]
+ defp increase_quotes_count_if_quote(%{
+ "object" => %{"quoteUrl" => quote_ap_id} = object,
+ "type" => "Create"
+ }) do
+ if public?(object) do
+ Object.increase_quotes_count(quote_ap_id)
+ end
+ end
+
+ defp increase_quotes_count_if_quote(_create_data), do: :noop
+
+ @object_types ~w[ChatMessage Question Answer Audio Video Image Event Article Note Page]
@impl true
def persist(%{"type" => type} = object, meta) when type in @object_types do
with {:ok, object} <- Object.create(object) do
@@ -136,9 +147,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
# Splice in the child object if we have one.
activity = Maps.put_if_present(activity, :object, object)
- ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
- Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
- end)
+ Pleroma.Web.RichMedia.Card.get_by_activity(activity)
+
+ # Add local posts to search index
+ if local, do: Pleroma.Search.add_to_index(activity)
{:ok, activity}
else
@@ -163,7 +175,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
id: "pleroma:fakeid"
}
- Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
+ Pleroma.Web.RichMedia.Card.get_by_activity(activity)
{:ok, activity}
{:remote_limit_pass, _} ->
@@ -188,7 +200,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def notify_and_stream(activity) do
- Notification.create_notifications(activity)
+ {:ok, notifications} = Notification.create_notifications(activity)
+ Notification.send(notifications)
original_activity =
case activity do
@@ -299,11 +312,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
with {:ok, activity} <- insert(create_data, local, fake),
{:fake, false, activity} <- {:fake, fake, activity},
_ <- increase_replies_count_if_reply(create_data),
+ _ <- increase_quotes_count_if_quote(create_data),
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
{:ok, _actor} <- update_last_status_at_if_public(actor, activity),
_ <- notify_and_stream(activity),
:ok <- maybe_schedule_poll_notifications(activity),
+ :ok <- maybe_handle_group_posts(activity),
:ok <- maybe_federate(activity) do
{:ok, activity}
else
@@ -455,6 +470,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> maybe_preload_objects(opts)
|> maybe_preload_bookmarks(opts)
|> maybe_set_thread_muted_field(opts)
+ |> restrict_unauthenticated(opts[:user])
|> restrict_blocked(opts)
|> restrict_blockers_visibility(opts)
|> restrict_recipients(recipients, opts[:user])
@@ -482,7 +498,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
@spec fetch_latest_direct_activity_id_for_context(String.t(), keyword() | map()) ::
- FlakeId.Ecto.CompatType.t() | nil
+ Ecto.UUID.t() | nil
def fetch_latest_direct_activity_id_for_context(context, opts \\ %{}) do
context
|> fetch_activities_for_context_query(Map.merge(%{skip_preload: true}, opts))
@@ -963,8 +979,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_replies(query, %{exclude_replies: true}) do
from(
- [_activity, object] in query,
- where: fragment("?->>'inReplyTo' is null", object.data)
+ [activity, object] in query,
+ where:
+ fragment("?->>'inReplyTo' is null or ?->>'type' = 'Announce'", object.data, activity.data)
)
end
@@ -1215,6 +1232,44 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_filtered(query, _), do: query
+ defp restrict_unauthenticated(query, nil) do
+ local = Config.restrict_unauthenticated_access?(:activities, :local)
+ remote = Config.restrict_unauthenticated_access?(:activities, :remote)
+
+ cond do
+ local and remote ->
+ from(activity in query, where: false)
+
+ local ->
+ from(activity in query, where: activity.local == false)
+
+ remote ->
+ from(activity in query, where: activity.local == true)
+
+ true ->
+ query
+ end
+ end
+
+ defp restrict_unauthenticated(query, _), do: query
+
+ defp restrict_quote_url(query, %{quote_url: quote_url}) do
+ from([_activity, object] in query,
+ where: fragment("(?)->'quoteUrl' = ?", object.data, ^quote_url)
+ )
+ end
+
+ defp restrict_quote_url(query, _), do: query
+
+ defp restrict_rule(query, %{rule_id: rule_id}) do
+ from(
+ activity in query,
+ where: fragment("(?)->'rules' \\? (?)", activity.data, ^rule_id)
+ )
+ end
+
+ defp restrict_rule(query, _), do: query
+
defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query
defp exclude_poll_votes(query, _) do
@@ -1377,6 +1432,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_instance(opts)
|> restrict_announce_object_actor(opts)
|> restrict_filtered(opts)
+ |> restrict_rule(opts)
+ |> restrict_quote_url(opts)
|> maybe_restrict_deactivated_users(opts)
|> exclude_poll_votes(opts)
|> exclude_chat_messages(opts)
@@ -1547,7 +1604,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
%{
ap_id: data["id"],
uri: get_actor_url(data["url"]),
- ap_enabled: true,
banner: normalize_image(data["image"]),
fields: fields,
emoji: emojis,
@@ -1652,9 +1708,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Fetcher.fetch_and_contain_remote_object_from_id(first) do
{:ok, false}
else
- {:error, {:ok, %{status: code}}} when code in [401, 403] -> {:ok, true}
- {:error, _} = e -> e
- e -> {:error, e}
+ {:error, _} -> {:ok, true}
end
end
@@ -1668,7 +1722,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
- def fetch_and_prepare_user_from_ap_id(ap_id, additional \\ []) do
+ defp fetch_and_prepare_user_from_ap_id(ap_id, additional) do
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
{:ok, data} <- user_data_from_user_object(data, additional) do
{:ok, maybe_update_follow_information(data)}
@@ -1721,6 +1775,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end)
end
+ def pin_data_from_featured_collection(obj) do
+ Logger.error("Could not parse featured collection #{inspect(obj)}")
+ %{}
+ end
+
def fetch_and_prepare_featured_from_ap_id(nil) do
{:ok, %{}}
end
@@ -1751,24 +1810,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def make_user_from_ap_id(ap_id, additional \\ []) do
user = User.get_cached_by_ap_id(ap_id)
- if user && !User.ap_enabled?(user) do
- Transmogrifier.upgrade_user_from_ap_id(ap_id)
- else
- with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, additional) do
- {:ok, _pid} = Task.start(fn -> pinned_fetch_task(data) end)
+ with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, additional) do
+ {:ok, _pid} = Task.start(fn -> pinned_fetch_task(data) end)
- if user do
- user
- |> User.remote_user_changeset(data)
- |> User.update_and_set_cache()
- else
- maybe_handle_clashing_nickname(data)
+ if user do
+ user
+ |> User.remote_user_changeset(data)
+ |> User.update_and_set_cache()
+ else
+ maybe_handle_clashing_nickname(data)
- data
- |> User.remote_user_changeset()
- |> Repo.insert()
- |> User.set_cache()
- end
+ data
+ |> User.remote_user_changeset()
+ |> Repo.insert()
+ |> User.set_cache()
end
end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 1357c379c..e38a94966 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -273,12 +273,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
- with %User{} = recipient <- User.get_cached_by_nickname(nickname),
- {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
+ with %User{is_active: true} = recipient <- User.get_cached_by_nickname(nickname),
+ {:ok, %User{is_active: true} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
true <- Utils.recipient_in_message(recipient, actor, params),
params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
Federator.incoming_ap_doc(params)
json(conn, "ok")
+ else
+ _ ->
+ conn
+ |> put_status(:bad_request)
+ |> json("Invalid request.")
end
end
@@ -287,10 +292,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
json(conn, "ok")
end
- def inbox(%{assigns: %{valid_signature: false}} = conn, _params) do
- conn
- |> put_status(:bad_request)
- |> json("Invalid HTTP Signature")
+ def inbox(%{assigns: %{valid_signature: false}, req_headers: req_headers} = conn, params) do
+ Federator.incoming_ap_doc(%{req_headers: req_headers, params: params})
+ json(conn, "ok")
end
# POST /relay/inbox -or- POST /internal/fetch/inbox
@@ -476,7 +480,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> json(message)
e ->
- Logger.warn(fn -> "AP C2S: #{inspect(e)}" end)
+ Logger.warning(fn -> "AP C2S: #{inspect(e)}" end)
conn
|> put_status(:bad_request)
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index 532047599..2a1e56278 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
This module encodes our addressing policies and general shape of our objects.
"""
+ alias Pleroma.Activity
alias Pleroma.Emoji
alias Pleroma.Object
alias Pleroma.User
@@ -16,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI.ActivityDraft
+ alias Pleroma.Web.Endpoint
require Pleroma.Constants
@@ -54,13 +56,87 @@ defmodule Pleroma.Web.ActivityPub.Builder do
{:ok, data, []}
end
+ defp unicode_emoji_react(_object, data, emoji) do
+ data
+ |> Map.put("content", emoji)
+ |> Map.put("type", "EmojiReact")
+ end
+
+ defp add_emoji_content(data, emoji, url) do
+ tag = [
+ %{
+ "id" => url,
+ "type" => "Emoji",
+ "name" => Emoji.maybe_quote(emoji),
+ "icon" => %{
+ "type" => "Image",
+ "url" => url
+ }
+ }
+ ]
+
+ data
+ |> Map.put("content", Emoji.maybe_quote(emoji))
+ |> Map.put("type", "EmojiReact")
+ |> Map.put("tag", tag)
+ end
+
+ defp remote_custom_emoji_react(
+ %{data: %{"reactions" => existing_reactions}},
+ data,
+ emoji
+ ) do
+ [emoji_code, instance] = String.split(Emoji.maybe_strip_name(emoji), "@")
+
+ matching_reaction =
+ Enum.find(
+ existing_reactions,
+ fn [name, _, url] ->
+ if url != nil do
+ url = URI.parse(url)
+ url.host == instance && name == emoji_code
+ end
+ end
+ )
+
+ if matching_reaction do
+ [name, _, url] = matching_reaction
+ add_emoji_content(data, name, url)
+ else
+ {:error, "Could not react"}
+ end
+ end
+
+ defp remote_custom_emoji_react(_object, _data, _emoji) do
+ {:error, "Could not react"}
+ end
+
+ defp local_custom_emoji_react(data, emoji) do
+ with %{file: path} = emojo <- Emoji.get(emoji) do
+ url = "#{Endpoint.url()}#{path}"
+ add_emoji_content(data, emojo.code, url)
+ else
+ _ -> {:error, "Emoji does not exist"}
+ end
+ end
+
+ defp custom_emoji_react(object, data, emoji) do
+ if String.contains?(emoji, "@") do
+ remote_custom_emoji_react(object, data, emoji)
+ else
+ local_custom_emoji_react(data, emoji)
+ end
+ end
+
@spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
def emoji_react(actor, object, emoji) do
with {:ok, data, meta} <- object_action(actor, object) do
data =
- data
- |> Map.put("content", emoji)
- |> Map.put("type", "EmojiReact")
+ if Emoji.unicode?(emoji) do
+ unicode_emoji_react(object, data, emoji)
+ else
+ custom_emoji_react(object, data, emoji)
+ end
{:ok, data, meta}
end
@@ -142,6 +218,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
"tag" => Keyword.values(draft.tags) |> Enum.uniq()
}
|> add_in_reply_to(draft.in_reply_to)
+ |> add_quote(draft.quote_post)
|> Map.merge(draft.extra)
{:ok, data, []}
@@ -157,6 +234,16 @@ defmodule Pleroma.Web.ActivityPub.Builder do
end
end
+ defp add_quote(object, nil), do: object
+
+ defp add_quote(object, quote_post) do
+ with %Object{} = quote_object <- Object.normalize(quote_post, fetch: false) do
+ Map.put(object, "quoteUrl", quote_object.data["id"])
+ else
+ _ -> object
+ end
+ end
+
def chat_message(actor, recipient, content, opts \\ []) do
basic = %{
"id" => Utils.generate_object_id(),
@@ -261,7 +348,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
actor.ap_id == Relay.ap_id() ->
[actor.follower_address]
- public? and Visibility.is_local_public?(object) ->
+ public? and Visibility.local_public?(object) ->
[actor.follower_address, object.data["actor"], Utils.as_local_public()]
public? ->
@@ -289,7 +376,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
# Address the actor of the object, and our actor's follower collection if the post is public.
to =
- if Visibility.is_public?(object) do
+ if Visibility.public?(object) do
[actor.follower_address, object.data["actor"]]
else
[object.data["actor"]]
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index ff9f84497..1071f8e6e 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF do
@@ -54,6 +54,8 @@ defmodule Pleroma.Web.ActivityPub.MRF do
@required_description_keys [:key, :related_policy]
def filter_one(policy, message) do
+ Code.ensure_loaded(policy)
+
should_plug_history? =
if function_exported?(policy, :history_awareness, 0) do
policy.history_awareness()
@@ -137,7 +139,16 @@ defmodule Pleroma.Web.ActivityPub.MRF do
@spec subdomains_regex([String.t()]) :: [Regex.t()]
def subdomains_regex(domains) when is_list(domains) do
- for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)i
+ for domain <- domains do
+ try do
+ target = String.replace(domain, "*.", "(.*\\.)*")
+ ~r<^#{target}$>i
+ rescue
+ e ->
+ Logger.error("MRF: Invalid subdomain Regex: #{domain}")
+ reraise e, __STACKTRACE__
+ end
+ end
end
@spec subdomain_match?([Regex.t()], String.t()) :: boolean()
@@ -188,6 +199,8 @@ defmodule Pleroma.Web.ActivityPub.MRF do
def config_descriptions(policies) do
Enum.reduce(policies, @mrf_config_descriptions, fn policy, acc ->
+ Code.ensure_loaded(policy)
+
if function_exported?(policy, :config_description, 0) do
description =
@default_description
@@ -199,7 +212,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
if Enum.all?(@required_description_keys, &Map.has_key?(description, &1)) do
[description | acc]
else
- Logger.warn(
+ Logger.warning(
"#{policy} config description doesn't have one or all required keys #{inspect(@required_description_keys)}"
)
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
index 97d75ecf2..df4ba819c 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
@@ -56,8 +56,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
nick_score + name_score + actor_type_score
end
- defp determine_if_followbot(_), do: 0.0
-
defp bot_allowed?(%{"object" => target}, bot_actor) do
%User{} = user = normalize_by_ap_id(target)
diff --git a/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex
new file mode 100644
index 000000000..f884962b9
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex
@@ -0,0 +1,281 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
+ require Pleroma.Constants
+
+ alias Pleroma.Object.Updater
+ alias Pleroma.Web.ActivityPub.MRF.Utils
+
+ @moduledoc "Reject or force-unlisted emojis with certain URLs or names"
+
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ defp config_remove_url do
+ Pleroma.Config.get([:mrf_emoji, :remove_url], [])
+ end
+
+ defp config_remove_shortcode do
+ Pleroma.Config.get([:mrf_emoji, :remove_shortcode], [])
+ end
+
+ defp config_unlist_url do
+ Pleroma.Config.get([:mrf_emoji, :federated_timeline_removal_url], [])
+ end
+
+ defp config_unlist_shortcode do
+ Pleroma.Config.get([:mrf_emoji, :federated_timeline_removal_shortcode], [])
+ end
+
+ @impl Pleroma.Web.ActivityPub.MRF.Policy
+ def history_awareness, do: :manual
+
+ @impl Pleroma.Web.ActivityPub.MRF.Policy
+ def filter(%{"type" => type, "object" => %{"type" => objtype} = object} = message)
+ when type in ["Create", "Update"] and objtype in Pleroma.Constants.status_object_types() do
+ with {:ok, object} <-
+ Updater.do_with_history(object, fn object ->
+ {:ok, process_remove(object, :url, config_remove_url())}
+ end),
+ {:ok, object} <-
+ Updater.do_with_history(object, fn object ->
+ {:ok, process_remove(object, :shortcode, config_remove_shortcode())}
+ end),
+ activity <- Map.put(message, "object", object),
+ activity <- maybe_delist(activity) do
+ {:ok, activity}
+ end
+ end
+
+ @impl Pleroma.Web.ActivityPub.MRF.Policy
+ def filter(%{"type" => type} = object) when type in Pleroma.Constants.actor_types() do
+ with object <- process_remove(object, :url, config_remove_url()),
+ object <- process_remove(object, :shortcode, config_remove_shortcode()) do
+ {:ok, object}
+ end
+ end
+
+ @impl Pleroma.Web.ActivityPub.MRF.Policy
+ def filter(%{"type" => "EmojiReact"} = object) do
+ with {:ok, _} <-
+ matched_emoji_checker(config_remove_url(), config_remove_shortcode()).(object) do
+ {:ok, object}
+ else
+ _ ->
+ {:reject, "[EmojiPolicy] Rejected for having disallowed emoji"}
+ end
+ end
+
+ @impl Pleroma.Web.ActivityPub.MRF.Policy
+ def filter(message) do
+ {:ok, message}
+ end
+
+ defp match_string?(string, pattern) when is_binary(pattern) do
+ string == pattern
+ end
+
+ defp match_string?(string, %Regex{} = pattern) do
+ String.match?(string, pattern)
+ end
+
+ defp match_any?(string, patterns) do
+ Enum.any?(patterns, &match_string?(string, &1))
+ end
+
+ defp url_from_tag(%{"icon" => %{"url" => url}}), do: url
+ defp url_from_tag(_), do: nil
+
+ defp url_from_emoji({_name, url}), do: url
+
+ defp shortcode_from_tag(%{"name" => name}) when is_binary(name), do: String.trim(name, ":")
+ defp shortcode_from_tag(_), do: nil
+
+ defp shortcode_from_emoji({name, _url}), do: name
+
+ defp process_remove(object, :url, patterns) do
+ process_remove_impl(object, &url_from_tag/1, &url_from_emoji/1, patterns)
+ end
+
+ defp process_remove(object, :shortcode, patterns) do
+ process_remove_impl(object, &shortcode_from_tag/1, &shortcode_from_emoji/1, patterns)
+ end
+
+ defp process_remove_impl(object, extract_from_tag, extract_from_emoji, patterns) do
+ object =
+ if object["tag"] do
+ Map.put(
+ object,
+ "tag",
+ Enum.filter(
+ object["tag"],
+ fn
+ %{"type" => "Emoji"} = tag ->
+ str = extract_from_tag.(tag)
+
+ if is_binary(str) do
+ not match_any?(str, patterns)
+ else
+ true
+ end
+
+ _ ->
+ true
+ end
+ )
+ )
+ else
+ object
+ end
+
+ object =
+ if object["emoji"] do
+ Map.put(
+ object,
+ "emoji",
+ object["emoji"]
+ |> Enum.reduce(%{}, fn {name, url} = emoji, acc ->
+ if not match_any?(extract_from_emoji.(emoji), patterns) do
+ Map.put(acc, name, url)
+ else
+ acc
+ end
+ end)
+ )
+ else
+ object
+ end
+
+ object
+ end
+
+ defp matched_emoji_checker(urls, shortcodes) do
+ fn object ->
+ if any_emoji_match?(object, &url_from_tag/1, &url_from_emoji/1, urls) or
+ any_emoji_match?(
+ object,
+ &shortcode_from_tag/1,
+ &shortcode_from_emoji/1,
+ shortcodes
+ ) do
+ {:matched, nil}
+ else
+ {:ok, %{}}
+ end
+ end
+ end
+
+ defp maybe_delist(%{"object" => object, "to" => to, "type" => "Create"} = activity) do
+ check = matched_emoji_checker(config_unlist_url(), config_unlist_shortcode())
+
+ should_delist? = fn object ->
+ with {:ok, _} <- Pleroma.Object.Updater.do_with_history(object, check) do
+ false
+ else
+ _ -> true
+ end
+ end
+
+ if Pleroma.Constants.as_public() in to and should_delist?.(object) do
+ to = List.delete(to, Pleroma.Constants.as_public())
+ cc = [Pleroma.Constants.as_public() | activity["cc"] || []]
+
+ activity
+ |> Map.put("to", to)
+ |> Map.put("cc", cc)
+ else
+ activity
+ end
+ end
+
+ defp maybe_delist(activity), do: activity
+
+ defp any_emoji_match?(object, extract_from_tag, extract_from_emoji, patterns) do
+ Kernel.||(
+ Enum.any?(
+ object["tag"] || [],
+ fn
+ %{"type" => "Emoji"} = tag ->
+ str = extract_from_tag.(tag)
+
+ if is_binary(str) do
+ match_any?(str, patterns)
+ else
+ false
+ end
+
+ _ ->
+ false
+ end
+ ),
+ (object["emoji"] || [])
+ |> Enum.any?(fn emoji -> match_any?(extract_from_emoji.(emoji), patterns) end)
+ )
+ end
+
+ @impl Pleroma.Web.ActivityPub.MRF.Policy
+ def describe do
+ mrf_emoji =
+ Pleroma.Config.get(:mrf_emoji, [])
+ |> Enum.map(fn {key, value} ->
+ {key, Enum.map(value, &Utils.describe_regex_or_string/1)}
+ end)
+ |> Enum.into(%{})
+
+ {:ok, %{mrf_emoji: mrf_emoji}}
+ end
+
+ @impl Pleroma.Web.ActivityPub.MRF.Policy
+ def config_description do
+ %{
+ key: :mrf_emoji,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.EmojiPolicy",
+ label: "MRF Emoji",
+ description:
+ "Reject or force-unlisted emojis whose URLs or names match a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).",
+ children: [
+ %{
+ key: :remove_url,
+ type: {:list, :string},
+ description: """
+ A list of patterns which result in emoji whose URL matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles.
+
+ Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+ """,
+ suggestions: ["https://example.org/foo.png", ~r/example.org\/foo/iu]
+ },
+ %{
+ key: :remove_shortcode,
+ type: {:list, :string},
+ description: """
+ A list of patterns which result in emoji whose shortcode matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles.
+
+ Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+ """,
+ suggestions: ["foo", ~r/foo/iu]
+ },
+ %{
+ key: :federated_timeline_removal_url,
+ type: {:list, :string},
+ description: """
+ A list of patterns which result in message with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses.
+
+ Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+ """,
+ suggestions: ["https://example.org/foo.png", ~r/example.org\/foo/iu]
+ },
+ %{
+ key: :federated_timeline_removal_shortcode,
+ type: {:list, :string},
+ description: """
+ A list of patterns which result in message with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses.
+
+ Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+ """,
+ suggestions: ["foo", ~r/foo/iu]
+ }
+ ]
+ }
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
index 5b6adbb4b..5a4a97626 100644
--- a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
@@ -19,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
try_follow(follower, message)
else
nil ->
- Logger.warn(
+ Logger.warning(
"#{__MODULE__} skipped because of missing `:mrf_follow_bot, :follower_nickname` configuration, the :follower_nickname
account does not exist, or the account is not correctly configured as a bot."
)
diff --git a/lib/pleroma/web/activity_pub/mrf/force_mention.ex b/lib/pleroma/web/activity_pub/mrf/force_mention.ex
new file mode 100644
index 000000000..3853489fc
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/force_mention.ex
@@ -0,0 +1,59 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.ForceMention do
+ require Pleroma.Constants
+
+ alias Pleroma.Config
+ alias Pleroma.Object
+ alias Pleroma.User
+
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ defp get_author(url) do
+ with %Object{data: %{"actor" => actor}} <- Object.normalize(url, fetch: false),
+ %User{ap_id: ap_id, nickname: nickname} <- User.get_cached_by_ap_id(actor) do
+ %{"type" => "Mention", "href" => ap_id, "name" => "@#{nickname}"}
+ else
+ _ -> nil
+ end
+ end
+
+ defp prepend_author(tags, _, false), do: tags
+
+ defp prepend_author(tags, nil, _), do: tags
+
+ defp prepend_author(tags, url, _) do
+ actor = get_author(url)
+
+ if not is_nil(actor) do
+ [actor | tags]
+ else
+ tags
+ end
+ end
+
+ @impl true
+ def filter(%{"type" => "Create", "object" => %{"tag" => tag} = object} = activity) do
+ tag =
+ tag
+ |> prepend_author(
+ object["inReplyTo"],
+ Config.get([:mrf_force_mention, :mention_parent, true])
+ )
+ |> prepend_author(
+ object["quoteUrl"],
+ Config.get([:mrf_force_mention, :mention_quoted, true])
+ )
+ |> Enum.uniq()
+
+ {:ok, put_in(activity["object"]["tag"], tag)}
+ end
+
+ @impl true
+ def filter(object), do: {:ok, object}
+
+ @impl true
+ def describe, do: {:ok, %{}}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex
index 70224561c..5532093cb 100644
--- a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex
+++ b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
@@ -95,11 +95,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
|> Enum.reject(&is_nil/1)
|> sort_replied_user(replied_to_user)
- explicitly_mentioned_uris = extract_mention_uris_from_content(content)
+ explicitly_mentioned_uris =
+ extract_mention_uris_from_content(content)
+ |> MapSet.new()
added_mentions =
- Enum.reduce(mention_users, "", fn %User{ap_id: uri} = user, acc ->
- unless uri in explicitly_mentioned_uris do
+ Enum.reduce(mention_users, "", fn %User{ap_id: ap_id, uri: uri} = user, acc ->
+ if MapSet.disjoint?(MapSet.new([ap_id, uri]), explicitly_mentioned_uris) do
acc <> Formatter.mention_from_user(user, %{mentions_format: :compact}) <> " "
else
acc
diff --git a/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex b/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex
index b73fd974c..fdb9a9dba 100644
--- a/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
alias Pleroma.Object
@moduledoc """
- Reject, TWKN-remove or Set-Sensitive messsages with specific hashtags (without the leading #)
+ Reject, TWKN-remove or Set-Sensitive messages with specific hashtags (without the leading #)
Note: This MRF Policy is always enabled, if you want to disable it you have to set empty lists.
"""
@@ -84,7 +84,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
if hashtags != [] do
with {:ok, message} <- check_reject(message, hashtags),
{:ok, message} <-
- (if "type" == "Create" do
+ (if type == "Create" do
check_ftl_removal(message, hashtags)
else
{:ok, message}
diff --git a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex
new file mode 100644
index 000000000..b7a01c27c
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex
@@ -0,0 +1,77 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do
+ @moduledoc "Force a quote line into the message content."
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ defp build_inline_quote(template, url) do
+ quote_line = String.replace(template, "{url}", "<a href=\"#{url}\">#{url}</a>")
+
+ "<span class=\"quote-inline\"><br/><br/>#{quote_line}</span>"
+ end
+
+ defp has_inline_quote?(content, quote_url) do
+ cond do
+ # Does the quote URL exist in the content?
+ content =~ quote_url -> true
+ # Does the content already have a .quote-inline span?
+ content =~ "<span class=\"quote-inline\">" -> true
+ # No inline quote found
+ true -> false
+ end
+ end
+
+ defp filter_object(%{"quoteUrl" => quote_url} = object) do
+ content = object["content"] || ""
+
+ if has_inline_quote?(content, quote_url) do
+ object
+ else
+ template = Pleroma.Config.get([:mrf_inline_quote, :template])
+
+ content =
+ if String.ends_with?(content, "</p>"),
+ do:
+ String.trim_trailing(content, "</p>") <>
+ build_inline_quote(template, quote_url) <> "</p>",
+ else: content <> build_inline_quote(template, quote_url)
+
+ Map.put(object, "content", content)
+ end
+ end
+
+ @impl true
+ def filter(%{"object" => %{"quoteUrl" => _} = object} = activity) do
+ {:ok, Map.put(activity, "object", filter_object(object))}
+ end
+
+ @impl true
+ def filter(object), do: {:ok, object}
+
+ @impl true
+ def describe, do: {:ok, %{}}
+
+ @impl Pleroma.Web.ActivityPub.MRF.Policy
+ def history_awareness, do: :auto
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_inline_quote,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy",
+ label: "MRF Inline Quote Policy",
+ description: "Force quote url to appear in post content.",
+ children: [
+ %{
+ key: :template,
+ type: :string,
+ description:
+ "The template to append to the post. `{url}` will be replaced with the actual link to the quoted post.",
+ suggestions: ["<bdi>RT:</bdi> {url}"]
+ }
+ ]
+ }
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
index 687ec6c2f..729da4e9c 100644
--- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
@@ -5,18 +5,17 @@
defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
require Pleroma.Constants
+ alias Pleroma.Web.ActivityPub.MRF.Utils
+
@moduledoc "Reject or Word-Replace messages with a keyword or regex"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
- defp string_matches?(string, _) when not is_binary(string) do
- false
- end
defp string_matches?(string, pattern) when is_binary(pattern) do
String.contains?(string, pattern)
end
- defp string_matches?(string, pattern) do
+ defp string_matches?(string, %Regex{} = pattern) do
String.match?(string, pattern)
end
@@ -128,7 +127,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
@impl true
def describe do
- # This horror is needed to convert regex sigils to strings
mrf_keyword =
Pleroma.Config.get(:mrf_keyword, [])
|> Enum.map(fn {key, value} ->
@@ -136,21 +134,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
Enum.map(value, fn
{pattern, replacement} ->
%{
- "pattern" =>
- if not is_binary(pattern) do
- inspect(pattern)
- else
- pattern
- end,
+ "pattern" => Utils.describe_regex_or_string(pattern),
"replacement" => replacement
}
pattern ->
- if not is_binary(pattern) do
- inspect(pattern)
- else
- pattern
- end
+ Utils.describe_regex_or_string(pattern)
end)}
end)
|> Enum.into(%{})
diff --git a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
index 855cda3b9..12bf4ddd2 100644
--- a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
@@ -10,9 +10,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do
@impl true
def filter(%{"actor" => actor} = object) do
- with true <- is_local?(actor),
- true <- is_eligible_type?(object),
- true <- is_note?(object),
+ with true <- local?(actor),
+ true <- eligible_type?(object),
+ true <- note?(object),
false <- has_attachment?(object),
true <- only_mentions?(object) do
{:reject, "[NoEmptyPolicy]"}
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do
def filter(object), do: {:ok, object}
- defp is_local?(actor) do
+ defp local?(actor) do
if actor |> String.starts_with?("#{Endpoint.url()}") do
true
else
@@ -59,11 +59,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do
defp only_mentions?(_), do: false
- defp is_note?(%{"object" => %{"type" => "Note"}}), do: true
- defp is_note?(_), do: false
+ defp note?(%{"object" => %{"type" => "Note"}}), do: true
+ defp note?(_), do: false
- defp is_eligible_type?(%{"type" => type}) when type in ["Create", "Update"], do: true
- defp is_eligible_type?(_), do: false
+ defp eligible_type?(%{"type" => type}) when type in ["Create", "Update"], do: true
+ defp eligible_type?(_), do: false
@impl true
def describe, do: {:ok, %{}}
diff --git a/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex
new file mode 100644
index 000000000..f7863039b
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex
@@ -0,0 +1,265 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do
+ @moduledoc """
+ Hide, delete, or mark sensitive NSFW content with artificial intelligence.
+
+ Requires a NSFW API server, configured like so:
+
+ config :pleroma, Pleroma.Web.ActivityPub.MRF.NsfwMRF,
+ url: "http://127.0.0.1:5000/",
+ threshold: 0.7,
+ mark_sensitive: true,
+ unlist: false,
+ reject: false
+
+ The NSFW API server must implement an HTTP endpoint like this:
+
+ curl http://localhost:5000/?url=https://fedi.com/images/001.jpg
+
+ Returning a response like this:
+
+ {"score", 0.314}
+
+ Where a score is 0-1, with `1` being definitely NSFW.
+
+ A good API server is here: https://github.com/EugenCepoi/nsfw_api
+ You can run it with Docker with a one-liner:
+
+ docker run -it -p 127.0.0.1:5000:5000/tcp --env PORT=5000 eugencepoi/nsfw_api:latest
+
+ Options:
+
+ - `url`: Base URL of the API server. Default: "http://127.0.0.1:5000/"
+ - `threshold`: Lowest score to take action on. Default: `0.7`
+ - `mark_sensitive`: Mark sensitive all detected NSFW content? Default: `true`
+ - `unlist`: Unlist all detected NSFW content? Default: `false`
+ - `reject`: Reject all detected NSFW content (takes precedence)? Default: `false`
+ """
+ alias Pleroma.Config
+ alias Pleroma.Constants
+ alias Pleroma.HTTP
+ alias Pleroma.User
+
+ require Logger
+ require Pleroma.Constants
+
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+ @policy :mrf_nsfw_api
+
+ def build_request_url(url) do
+ Config.get([@policy, :url])
+ |> URI.parse()
+ |> fix_path()
+ |> Map.put(:query, "url=#{url}")
+ |> URI.to_string()
+ end
+
+ def parse_url(url) do
+ request = build_request_url(url)
+
+ with {:ok, %Tesla.Env{body: body}} <- HTTP.get(request) do
+ Jason.decode(body)
+ else
+ error ->
+ Logger.warn("""
+ [NsfwApiPolicy]: The API server failed. Skipping.
+ #{inspect(error)}
+ """)
+
+ error
+ end
+ end
+
+ def check_url_nsfw(url) when is_binary(url) do
+ threshold = Config.get([@policy, :threshold])
+
+ case parse_url(url) do
+ {:ok, %{"score" => score}} when score >= threshold ->
+ {:nsfw, %{url: url, score: score, threshold: threshold}}
+
+ {:ok, %{"score" => score}} ->
+ {:sfw, %{url: url, score: score, threshold: threshold}}
+
+ _ ->
+ {:sfw, %{url: url, score: nil, threshold: threshold}}
+ end
+ end
+
+ def check_url_nsfw(%{"href" => url}) when is_binary(url) do
+ check_url_nsfw(url)
+ end
+
+ def check_url_nsfw(url) do
+ threshold = Config.get([@policy, :threshold])
+ {:sfw, %{url: url, score: nil, threshold: threshold}}
+ end
+
+ def check_attachment_nsfw(%{"url" => urls} = attachment) when is_list(urls) do
+ if Enum.all?(urls, &match?({:sfw, _}, check_url_nsfw(&1))) do
+ {:sfw, attachment}
+ else
+ {:nsfw, attachment}
+ end
+ end
+
+ def check_attachment_nsfw(%{"url" => url} = attachment) when is_binary(url) do
+ case check_url_nsfw(url) do
+ {:sfw, _} -> {:sfw, attachment}
+ {:nsfw, _} -> {:nsfw, attachment}
+ end
+ end
+
+ def check_attachment_nsfw(attachment), do: {:sfw, attachment}
+
+ def check_object_nsfw(%{"attachment" => attachments} = object) when is_list(attachments) do
+ if Enum.all?(attachments, &match?({:sfw, _}, check_attachment_nsfw(&1))) do
+ {:sfw, object}
+ else
+ {:nsfw, object}
+ end
+ end
+
+ def check_object_nsfw(%{"object" => %{} = child_object} = object) do
+ case check_object_nsfw(child_object) do
+ {:sfw, _} -> {:sfw, object}
+ {:nsfw, _} -> {:nsfw, object}
+ end
+ end
+
+ def check_object_nsfw(object), do: {:sfw, object}
+
+ @impl true
+ def filter(object) do
+ with {:sfw, object} <- check_object_nsfw(object) do
+ {:ok, object}
+ else
+ {:nsfw, _data} -> handle_nsfw(object)
+ _ -> {:reject, "NSFW: Attachment rejected"}
+ end
+ end
+
+ defp handle_nsfw(object) do
+ if Config.get([@policy, :reject]) do
+ {:reject, object}
+ else
+ {:ok,
+ object
+ |> maybe_unlist()
+ |> maybe_mark_sensitive()}
+ end
+ end
+
+ defp maybe_unlist(object) do
+ if Config.get([@policy, :unlist]) do
+ unlist(object)
+ else
+ object
+ end
+ end
+
+ defp maybe_mark_sensitive(object) do
+ if Config.get([@policy, :mark_sensitive]) do
+ mark_sensitive(object)
+ else
+ object
+ end
+ end
+
+ def unlist(%{"to" => to, "cc" => cc, "actor" => actor} = object) do
+ with %User{} = user <- User.get_cached_by_ap_id(actor) do
+ to =
+ [user.follower_address | to]
+ |> List.delete(Constants.as_public())
+ |> Enum.uniq()
+
+ cc =
+ [Constants.as_public() | cc]
+ |> List.delete(user.follower_address)
+ |> Enum.uniq()
+
+ object
+ |> Map.put("to", to)
+ |> Map.put("cc", cc)
+ else
+ _ -> raise "[NsfwApiPolicy]: Could not find user #{actor}"
+ end
+ end
+
+ def mark_sensitive(%{"object" => child_object} = object) when is_map(child_object) do
+ Map.put(object, "object", mark_sensitive(child_object))
+ end
+
+ def mark_sensitive(object) when is_map(object) do
+ tags = (object["tag"] || []) ++ ["nsfw"]
+
+ object
+ |> Map.put("tag", tags)
+ |> Map.put("sensitive", true)
+ end
+
+ # Hackney needs a trailing slash
+ defp fix_path(%URI{path: path} = uri) when is_binary(path) do
+ path = String.trim_trailing(path, "/") <> "/"
+ Map.put(uri, :path, path)
+ end
+
+ defp fix_path(%URI{path: nil} = uri), do: Map.put(uri, :path, "/")
+
+ @impl true
+ def describe do
+ options = %{
+ threshold: Config.get([@policy, :threshold]),
+ mark_sensitive: Config.get([@policy, :mark_sensitive]),
+ unlist: Config.get([@policy, :unlist]),
+ reject: Config.get([@policy, :reject])
+ }
+
+ {:ok, %{@policy => options}}
+ end
+
+ @impl true
+ def config_description do
+ %{
+ key: @policy,
+ related_policy: to_string(__MODULE__),
+ label: "NSFW API Policy",
+ description:
+ "Hide, delete, or mark sensitive NSFW content with artificial intelligence. Requires running an external API server.",
+ children: [
+ %{
+ key: :url,
+ type: :string,
+ description: "Base URL of the API server.",
+ suggestions: ["http://127.0.0.1:5000/"]
+ },
+ %{
+ key: :threshold,
+ type: :float,
+ description: "Lowest score to take action on. Between 0 and 1.",
+ suggestions: [0.7]
+ },
+ %{
+ key: :mark_sensitive,
+ type: :boolean,
+ description: "Mark sensitive all detected NSFW content?",
+ suggestions: [true]
+ },
+ %{
+ key: :unlist,
+ type: :boolean,
+ description: "Unlist sensitive all detected NSFW content?",
+ suggestions: [false]
+ },
+ %{
+ key: :reject,
+ type: :boolean,
+ description: "Reject sensitive all detected NSFW content (takes precedence)?",
+ suggestions: [false]
+ }
+ ]
+ }
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/policy.ex b/lib/pleroma/web/activity_pub/mrf/policy.ex
index 0234de4d5..1f34883e7 100644
--- a/lib/pleroma/web/activity_pub/mrf/policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/policy.ex
@@ -3,8 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.Policy do
- @callback filter(Map.t()) :: {:ok | :reject, Map.t()}
- @callback describe() :: {:ok | :error, Map.t()}
+ @callback filter(map()) :: {:ok | :reject, map()}
+ @callback describe() :: {:ok | :error, map()}
@callback config_description() :: %{
optional(:children) => [map()],
key: atom(),
diff --git a/lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex
new file mode 100644
index 000000000..ac353f03f
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex
@@ -0,0 +1,49 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy do
+ @moduledoc "Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions)"
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+
+ require Pleroma.Constants
+
+ @impl Pleroma.Web.ActivityPub.MRF.Policy
+ def filter(%{"object" => %{"quoteUrl" => _} = object} = activity) do
+ {:ok, Map.put(activity, "object", filter_object(object))}
+ end
+
+ @impl Pleroma.Web.ActivityPub.MRF.Policy
+ def filter(object), do: {:ok, object}
+
+ @impl Pleroma.Web.ActivityPub.MRF.Policy
+ def describe, do: {:ok, %{}}
+
+ @impl Pleroma.Web.ActivityPub.MRF.Policy
+ def history_awareness, do: :auto
+
+ defp filter_object(%{"quoteUrl" => quote_url} = object) do
+ tags = object["tag"] || []
+
+ if Enum.any?(tags, fn tag ->
+ CommonFixes.object_link_tag?(tag) and tag["href"] == quote_url
+ end) do
+ object
+ else
+ object
+ |> Map.put(
+ "tag",
+ tags ++
+ [
+ %{
+ "type" => "Link",
+ "mediaType" => Pleroma.Constants.activity_json_canonical_mime_type(),
+ "href" => quote_url
+ }
+ ]
+ )
+ end
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
index f66c379b5..fa6b595ea 100644
--- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
@@ -34,14 +34,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
|> Path.basename()
|> Path.extname()
- file_path = Path.join(emoji_dir_path, shortcode <> (extension || ".png"))
+ extension = if extension == "", do: ".png", else: extension
+
+ shortcode = Path.basename(shortcode)
+ file_path = Path.join(emoji_dir_path, shortcode <> extension)
case File.write(file_path, response.body) do
:ok ->
shortcode
e ->
- Logger.warn("MRF.StealEmojiPolicy: Failed to write to #{file_path}: #{inspect(e)}")
+ Logger.warning("MRF.StealEmojiPolicy: Failed to write to #{file_path}: #{inspect(e)}")
nil
end
else
@@ -53,7 +56,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
end
else
e ->
- Logger.warn("MRF.StealEmojiPolicy: Failed to fetch #{url}: #{inspect(e)}")
+ Logger.warning("MRF.StealEmojiPolicy: Failed to fetch #{url}: #{inspect(e)}")
nil
end
end
@@ -76,6 +79,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
new_emojis =
foreign_emojis
|> Enum.reject(fn {shortcode, _url} -> shortcode in installed_emoji end)
+ |> Enum.reject(fn {shortcode, _url} -> String.contains?(shortcode, ["/", "\\"]) end)
|> Enum.filter(fn {shortcode, _url} ->
reject_emoji? =
[:mrf_steal_emoji, :rejected_shortcodes]
diff --git a/lib/pleroma/web/activity_pub/mrf/utils.ex b/lib/pleroma/web/activity_pub/mrf/utils.ex
new file mode 100644
index 000000000..f2dc9eea9
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/utils.ex
@@ -0,0 +1,15 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.Utils do
+ @spec describe_regex_or_string(String.t() | Regex.t()) :: String.t()
+ def describe_regex_or_string(pattern) do
+ # This horror is needed to convert regex sigils to strings
+ if not is_binary(pattern) do
+ inspect(pattern)
+ else
+ pattern
+ end
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 5bcd6da46..b3043b93a 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -21,7 +21,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
@@ -102,7 +102,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
%{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
meta
)
- when objtype in ~w[Question Answer Audio Video Event Article Note Page] do
+ when objtype in ~w[Question Answer Audio Video Image Event Article Note Page] do
with {:ok, object_data} <- cast_and_apply_and_stringify_with_history(object),
meta = Keyword.put(meta, :object_data, object_data),
{:ok, create_activity} <-
@@ -115,13 +115,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
end
def validate(%{"type" => type} = object, meta)
- when type in ~w[Event Question Audio Video Article Note Page] do
+ when type in ~w[Event Question Audio Video Image Article Note Page] do
validator =
case type do
"Event" -> EventValidator
"Question" -> QuestionValidator
- "Audio" -> AudioVideoValidator
- "Video" -> AudioVideoValidator
+ "Audio" -> AudioImageVideoValidator
+ "Video" -> AudioImageVideoValidator
+ "Image" -> AudioImageVideoValidator
"Article" -> ArticleNotePageValidator
"Note" -> ArticleNotePageValidator
"Page" -> ArticleNotePageValidator
@@ -172,6 +173,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
{:object_validation, e} ->
e
+
+ {:error, %Ecto.Changeset{} = e} ->
+ {:error, e}
end
end
@@ -233,8 +237,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
AnswerValidator.cast_and_apply(object)
end
- def cast_and_apply(%{"type" => type} = object) when type in ~w[Audio Video] do
- AudioVideoValidator.cast_and_apply(object)
+ def cast_and_apply(%{"type" => type} = object) when type in ~w[Audio Image Video] do
+ AudioImageVideoValidator.cast_and_apply(object)
end
def cast_and_apply(%{"type" => "Event"} = object) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
index 5202db7f1..db3259550 100644
--- a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
@@ -73,6 +73,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
end
defp maybe_refetch_user(%User{ap_id: ap_id}) do
- Pleroma.Web.ActivityPub.Transmogrifier.upgrade_user_from_ap_id(ap_id)
+ # Maybe it could use User.get_or_fetch_by_ap_id to avoid refreshing too often
+ User.fetch_by_ap_id(ap_id)
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
index c2c7ba1a8..d0218583e 100644
--- a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
@@ -82,7 +82,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
object when is_binary(object) <- get_field(cng, :object),
%User{} = actor <- User.get_cached_by_ap_id(actor),
%Object{} = object <- Object.get_cached_by_ap_id(object),
- false <- Visibility.is_public?(object) do
+ false <- Visibility.public?(object) do
same_actor = object.data["actor"] == actor.ap_id
recipients = get_field(cng, :to) ++ get_field(cng, :cc)
local_public = Utils.as_local_public()
diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
index 2670e3f17..1b5b2e8fb 100644
--- a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
@@ -84,6 +84,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|> fix_tag()
|> fix_replies()
|> fix_attachments()
+ |> CommonFixes.fix_quote_url()
|> Transmogrifier.fix_emoji()
|> Transmogrifier.fix_content_map()
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
index 766421e60..5ee9e7549 100644
--- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -12,14 +12,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
@primary_key false
embedded_schema do
field(:id, :string)
- field(:type, :string)
+ field(:type, :string, default: "Link")
field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream")
field(:name, :string)
field(:summary, :string)
field(:blurhash, :string)
embeds_many :url, UrlObjectValidator, primary_key: false do
- field(:type, :string)
+ field(:type, :string, default: "Link")
field(:href, ObjectValidators.Uri)
field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream")
field(:width, :integer)
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex
index 671a7ef0c..65ac6bb93 100644
--- a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex
@@ -2,7 +2,7 @@
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator do
use Ecto.Schema
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
@@ -55,9 +55,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
url
|> Enum.concat(mpeg_url["tag"] || [])
|> Enum.find(fn
- %{"mediaType" => mime_type} -> String.starts_with?(mime_type, ["video/", "audio/"])
- %{"mimeType" => mime_type} -> String.starts_with?(mime_type, ["video/", "audio/"])
- _ -> false
+ %{"mediaType" => mime_type} ->
+ String.starts_with?(mime_type, ["video/", "audio/", "image/"])
+
+ %{"mimeType" => mime_type} ->
+ String.starts_with?(mime_type, ["video/", "audio/", "image/"])
+
+ _ ->
+ false
end)
end
@@ -94,6 +99,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
data
|> CommonFixes.fix_actor()
|> CommonFixes.fix_object_defaults()
+ |> CommonFixes.fix_quote_url()
|> Transmogrifier.fix_emoji()
|> fix_url()
|> fix_content()
@@ -110,7 +116,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
defp validate_data(data_cng) do
data_cng
- |> validate_inclusion(:type, ["Audio", "Video"])
+ |> validate_inclusion(:type, ~w[Audio Image Video])
|> validate_required([:id, :actor, :attributedTo, :type, :context])
|> CommonValidations.validate_any_presence([:cc, :to])
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
diff --git a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
index efae48cae..09e25be89 100644
--- a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
@@ -57,6 +57,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
|> Map.put("attachment", attachment)
end
+ def fix_attachment(%{"attachment" => attachment} = data) when attachment == [] do
+ data
+ |> Map.drop(["attachment"])
+ end
+
def fix_attachment(data), do: data
def changeset(struct, data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex
index 7b60c139a..1a5d02601 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex
@@ -27,7 +27,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
end
end
- # All objects except Answer and CHatMessage
+ # All objects except Answer and ChatMessage
defmacro object_fields do
quote bind_quoted: binding() do
field(:content, :string)
@@ -57,8 +57,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
+ field(:quotes_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
- field(:url, ObjectValidators.Uri)
+ field(:quoteUrl, ObjectValidators.ObjectID)
+ field(:url, ObjectValidators.BareUri)
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
index add46d561..4699029d4 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
@@ -4,12 +4,15 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Maps
alias Pleroma.Object
alias Pleroma.Object.Containment
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
+ require Pleroma.Constants
+
def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do
{:ok, data} = ObjectValidators.Recipients.cast(message[field] || field_fallback)
@@ -22,6 +25,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
end
def fix_object_defaults(data) do
+ data = Maps.filter_empty_values(data)
+
context =
Utils.maybe_create_context(
data["context"] || data["conversation"] || data["inReplyTo"] || data["id"]
@@ -76,4 +81,48 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
Map.put(data, "to", to)
end
+
+ def fix_quote_url(%{"quoteUrl" => _quote_url} = data), do: data
+
+ # Fedibird
+ # https://github.com/fedibird/mastodon/commit/dbd7ae6cf58a92ec67c512296b4daaea0d01e6ac
+ def fix_quote_url(%{"quoteUri" => quote_url} = data) do
+ Map.put(data, "quoteUrl", quote_url)
+ end
+
+ # Old Fedibird (bug)
+ # https://github.com/fedibird/mastodon/issues/9
+ def fix_quote_url(%{"quoteURL" => quote_url} = data) do
+ Map.put(data, "quoteUrl", quote_url)
+ end
+
+ # Misskey fallback
+ def fix_quote_url(%{"_misskey_quote" => quote_url} = data) do
+ Map.put(data, "quoteUrl", quote_url)
+ end
+
+ def fix_quote_url(%{"tag" => [_ | _] = tags} = data) do
+ tag = Enum.find(tags, &object_link_tag?/1)
+
+ if not is_nil(tag) do
+ data
+ |> Map.put("quoteUrl", tag["href"])
+ else
+ data
+ end
+ end
+
+ def fix_quote_url(data), do: data
+
+ # https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md
+ def object_link_tag?(%{
+ "type" => "Link",
+ "mediaType" => media_type,
+ "href" => href
+ })
+ when media_type in Pleroma.Constants.activity_json_mime_types() and is_binary(href) do
+ true
+ end
+
+ def object_link_tag?(_), do: false
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
index 0858281e5..65ba047e6 100644
--- a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
@@ -5,8 +5,10 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
use Ecto.Schema
+ alias Pleroma.Emoji
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+ alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -19,6 +21,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
activity_fields()
+ embeds_many(:tag, TagValidator)
end
end
@@ -43,7 +46,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
def changeset(struct, data) do
struct
- |> cast(data, __schema__(:fields))
+ |> cast(data, __schema__(:fields) -- [:tag])
+ |> cast_embed(:tag)
end
defp fix(data) do
@@ -53,12 +57,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|> CommonFixes.fix_actor()
|> CommonFixes.fix_activity_addressing()
- with %Object{} = object <- Object.normalize(data["object"]) do
- data
- |> CommonFixes.fix_activity_context(object)
- |> CommonFixes.fix_object_action_recipients(object)
- else
- _ -> data
+ data = Map.put_new(data, "tag", [])
+
+ case Object.normalize(data["object"]) do
+ %Object{} = object ->
+ data
+ |> CommonFixes.fix_activity_context(object)
+ |> CommonFixes.fix_object_action_recipients(object)
+
+ _ ->
+ data
end
end
@@ -66,10 +74,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
new_emoji = Pleroma.Emoji.fully_qualify_emoji(emoji)
cond do
- Pleroma.Emoji.is_unicode_emoji?(emoji) ->
+ Pleroma.Emoji.unicode?(emoji) ->
data
- Pleroma.Emoji.is_unicode_emoji?(new_emoji) ->
+ Pleroma.Emoji.unicode?(new_emoji) ->
data |> Map.put("content", new_emoji)
true ->
@@ -82,11 +90,31 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
defp validate_emoji(cng) do
content = get_field(cng, :content)
- if Pleroma.Emoji.is_unicode_emoji?(content) do
+ if Emoji.unicode?(content) || Emoji.custom?(content) do
cng
else
cng
- |> add_error(:content, "must be a single character emoji")
+ |> add_error(:content, "is not a valid emoji")
+ end
+ end
+
+ defp maybe_validate_tag_presence(cng) do
+ content = get_field(cng, :content)
+
+ if Emoji.unicode?(content) do
+ cng
+ else
+ tag = get_field(cng, :tag)
+ emoji_name = Emoji.maybe_strip_name(content)
+
+ case tag do
+ [%{name: ^emoji_name, type: "Emoji", icon: %{url: _}}] ->
+ cng
+
+ _ ->
+ cng
+ |> add_error(:tag, "does not contain an Emoji tag")
+ end
end
end
@@ -97,5 +125,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|> validate_actor_presence()
|> validate_object_presence()
|> validate_emoji()
+ |> maybe_validate_tag_presence()
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex
index 541945fa4..8d7f7b9fa 100644
--- a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex
@@ -14,10 +14,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator do
embeds_one :replies, Replies, primary_key: false do
field(:totalItems, :integer)
- field(:type, :string)
+ field(:type, :string, default: "Collection")
end
- field(:type, :string)
+ field(:type, :string, default: "Note")
end
def changeset(struct, data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
index ce3305142..7f9d4d648 100644
--- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
@@ -29,6 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
field(:closed, ObjectValidators.DateTime)
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
+ field(:nonAnonymous, :boolean)
embeds_many(:anyOf, QuestionOptionsValidator)
embeds_many(:oneOf, QuestionOptionsValidator)
end
@@ -62,6 +63,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
data
|> CommonFixes.fix_actor()
|> CommonFixes.fix_object_defaults()
+ |> CommonFixes.fix_quote_url()
|> Transmogrifier.fix_emoji()
|> fix_closed()
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex
index 9f15f1981..47cf7b415 100644
--- a/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex
@@ -9,15 +9,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
import Ecto.Changeset
+ require Pleroma.Constants
+
@primary_key false
embedded_schema do
# Common
field(:type, :string)
field(:name, :string)
- # Mention, Hashtag
+ # Mention, Hashtag, Link
field(:href, ObjectValidators.Uri)
+ # Link
+ field(:mediaType, :string)
+
# Emoji
embeds_one :icon, IconObjectValidator, primary_key: false do
field(:type, :string)
@@ -68,6 +73,19 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
|> validate_required([:type, :name, :icon])
end
+ def changeset(struct, %{"type" => "Link"} = data) do
+ struct
+ |> cast(data, [:type, :name, :mediaType, :href])
+ |> validate_inclusion(:mediaType, Pleroma.Constants.activity_json_mime_types())
+ |> validate_required([:type, :href, :mediaType])
+ end
+
+ def changeset(struct, %{"type" => _} = data) do
+ struct
+ |> cast(data, [])
+ |> Map.put(:action, :ignore)
+ end
+
def icon_changeset(struct, data) do
struct
|> cast(data, [:type, :url])
diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
index ca8653ab1..40184bd97 100644
--- a/lib/pleroma/web/activity_pub/pipeline.ex
+++ b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -62,7 +62,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
with {:ok, local} <- Keyword.fetch(meta, :local) do
do_not_federate = meta[:do_not_federate] || !config().get([:instance, :federating])
- if !do_not_federate and local and not Visibility.is_local_public?(activity) do
+ if !do_not_federate and local and not Visibility.local_public?(activity) do
activity =
if object = Keyword.get(meta, :object_data) do
%{activity | data: Map.put(activity.data, "object", object)}
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 6c1ba76a3..a42b4844e 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -13,13 +13,12 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Workers.PublisherWorker
require Pleroma.Constants
import Pleroma.Web.ActivityPub.Visibility
- @behaviour Pleroma.Web.Federator.Publisher
-
require Logger
@moduledoc """
@@ -27,9 +26,47 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
"""
@doc """
+ Enqueue publishing a single activity.
+ """
+ @spec enqueue_one(map(), Keyword.t()) :: {:ok, %Oban.Job{}}
+ def enqueue_one(%{} = params, worker_args \\ []) do
+ PublisherWorker.enqueue(
+ "publish_one",
+ %{"params" => params},
+ worker_args
+ )
+ end
+
+ @doc """
+ Gathers a set of remote users given an IR envelope.
+ """
+ def remote_users(%User{id: user_id}, %{data: %{"to" => to} = data}) do
+ cc = Map.get(data, "cc", [])
+
+ bcc =
+ data
+ |> Map.get("bcc", [])
+ |> Enum.reduce([], fn ap_id, bcc ->
+ case Pleroma.List.get_by_ap_id(ap_id) do
+ %Pleroma.List{user_id: ^user_id} = list ->
+ {:ok, following} = Pleroma.List.get_following(list)
+ bcc ++ Enum.map(following, & &1.ap_id)
+
+ _ ->
+ bcc
+ end
+ end)
+
+ [to, cc, bcc]
+ |> Enum.concat()
+ |> Enum.map(&User.get_cached_by_ap_id/1)
+ |> Enum.filter(fn user -> user && !user.local end)
+ end
+
+ @doc """
Determine if an activity can be represented by running it through Transmogrifier.
"""
- def is_representable?(%Activity{} = activity) do
+ def representable?(%Activity{} = activity) do
with {:ok, _data} <- Transmogrifier.prepare_outgoing(activity.data) do
true
else
@@ -80,9 +117,27 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
result
else
- {_post_result, response} ->
+ {_post_result, %{status: code} = response} = e ->
+ unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
+ Logger.metadata(activity: id, inbox: inbox, status: code)
+ Logger.error("Publisher failed to inbox #{inbox} with status #{code}")
+
+ case response do
+ %{status: 403} -> {:discard, :forbidden}
+ %{status: 404} -> {:discard, :not_found}
+ %{status: 410} -> {:discard, :not_found}
+ _ -> {:error, e}
+ end
+
+ {:error, :pool_full} ->
+ Logger.debug("Publisher snoozing worker job due to full connection pool")
+ {:snooze, 30}
+
+ e ->
unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
- {:error, response}
+ Logger.metadata(activity: id, inbox: inbox)
+ Logger.error("Publisher failed to inbox #{inbox} #{inspect(e)}")
+ {:error, e}
end
end
@@ -103,22 +158,21 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
end
end
- defp should_federate?(inbox, public) do
- if public do
- true
- else
- %{host: host} = URI.parse(inbox)
+ def should_federate?(nil, _), do: false
+ def should_federate?(_, true), do: true
- quarantined_instances =
- Config.get([:instance, :quarantined_instances], [])
- |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
- |> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
+ def should_federate?(inbox, _) do
+ %{host: host} = URI.parse(inbox)
- !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
- end
+ quarantined_instances =
+ Config.get([:instance, :quarantined_instances], [])
+ |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
+ |> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
+
+ !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
end
- @spec recipients(User.t(), Activity.t()) :: list(User.t()) | []
+ @spec recipients(User.t(), Activity.t()) :: [[User.t()]]
defp recipients(actor, activity) do
followers =
if actor.follower_address in activity.recipients do
@@ -138,7 +192,10 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
[]
end
- Pleroma.Web.Federator.Publisher.remote_users(actor, activity) ++ followers ++ fetchers
+ mentioned = remote_users(actor, activity)
+ non_mentioned = (followers ++ fetchers) -- mentioned
+
+ [mentioned, non_mentioned]
end
defp get_cc_ap_ids(ap_id, recipients) do
@@ -192,45 +249,52 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
def publish(%User{} = actor, %{data: %{"bcc" => bcc}} = activity)
when is_list(bcc) and bcc != [] do
- public = is_public?(activity)
+ public = public?(activity)
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
- recipients = recipients(actor, activity)
+ [priority_recipients, recipients] = recipients(actor, activity)
inboxes =
- recipients
- |> Enum.filter(&User.ap_enabled?/1)
- |> Enum.map(fn actor -> actor.inbox end)
- |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
- |> Instances.filter_reachable()
+ [priority_recipients, recipients]
+ |> Enum.map(fn recipients ->
+ recipients
+ |> Enum.map(fn %User{} = user ->
+ determine_inbox(activity, user)
+ end)
+ |> Enum.uniq()
+ |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
+ |> Instances.filter_reachable()
+ end)
Repo.checkout(fn ->
- Enum.each(inboxes, fn {inbox, unreachable_since} ->
- %User{ap_id: ap_id} = Enum.find(recipients, fn actor -> actor.inbox == inbox end)
-
- # Get all the recipients on the same host and add them to cc. Otherwise, a remote
- # instance would only accept a first message for the first recipient and ignore the rest.
- cc = get_cc_ap_ids(ap_id, recipients)
-
- json =
- data
- |> Map.put("cc", cc)
- |> Jason.encode!()
-
- Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{
- inbox: inbox,
- json: json,
- actor_id: actor.id,
- id: activity.data["id"],
- unreachable_since: unreachable_since
- })
+ Enum.each(inboxes, fn inboxes ->
+ Enum.each(inboxes, fn {inbox, unreachable_since} ->
+ %User{ap_id: ap_id} = Enum.find(recipients, fn actor -> actor.inbox == inbox end)
+
+ # Get all the recipients on the same host and add them to cc. Otherwise, a remote
+ # instance would only accept a first message for the first recipient and ignore the rest.
+ cc = get_cc_ap_ids(ap_id, recipients)
+
+ json =
+ data
+ |> Map.put("cc", cc)
+ |> Jason.encode!()
+
+ __MODULE__.enqueue_one(%{
+ inbox: inbox,
+ json: json,
+ actor_id: actor.id,
+ id: activity.data["id"],
+ unreachable_since: unreachable_since
+ })
+ end)
end)
end)
end
# Publishes an activity to all relevant peers.
def publish(%User{} = actor, %Activity{} = activity) do
- public = is_public?(activity)
+ public = public?(activity)
if public && Config.get([:instance, :allow_relay]) do
Logger.debug(fn -> "Relaying #{activity.data["id"]} out" end)
@@ -240,26 +304,38 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
json = Jason.encode!(data)
- recipients(actor, activity)
- |> Enum.filter(fn user -> User.ap_enabled?(user) end)
- |> Enum.map(fn %User{} = user ->
- determine_inbox(activity, user)
- end)
- |> Enum.uniq()
- |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
- |> Instances.filter_reachable()
- |> Enum.each(fn {inbox, unreachable_since} ->
- Pleroma.Web.Federator.Publisher.enqueue_one(
- __MODULE__,
- %{
- inbox: inbox,
- json: json,
- actor_id: actor.id,
- id: activity.data["id"],
- unreachable_since: unreachable_since
- }
- )
+ [priority_inboxes, inboxes] =
+ recipients(actor, activity)
+ |> Enum.map(fn recipients ->
+ recipients
+ |> Enum.map(fn %User{} = user ->
+ determine_inbox(activity, user)
+ end)
+ |> Enum.uniq()
+ |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
+ end)
+
+ inboxes = inboxes -- priority_inboxes
+
+ [{priority_inboxes, 0}, {inboxes, 1}]
+ |> Enum.each(fn {inboxes, priority} ->
+ inboxes
+ |> Instances.filter_reachable()
+ |> Enum.each(fn {inbox, unreachable_since} ->
+ __MODULE__.enqueue_one(
+ %{
+ inbox: inbox,
+ json: json,
+ actor_id: actor.id,
+ id: activity.data["id"],
+ unreachable_since: unreachable_since
+ },
+ priority: priority
+ )
+ end)
end)
+
+ :ok
end
def gather_webfinger_links(%User{} = user) do
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index 2010351d1..91a647f29 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -58,7 +58,7 @@ defmodule Pleroma.Web.ActivityPub.Relay do
@spec publish(any()) :: {:ok, Activity.t()} | {:error, any()}
def publish(%Activity{data: %{"type" => "Create"}} = activity) do
with %User{} = user <- get_actor(),
- true <- Visibility.is_public?(activity) do
+ true <- Visibility.public?(activity) do
CommonAPI.repeat(activity.id, user)
else
error -> format_error(error)
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index a2152b945..60b4d5f1b 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -21,7 +21,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
- alias Pleroma.Web.Push
alias Pleroma.Web.Streamer
alias Pleroma.Workers.PollWorker
@@ -125,7 +124,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
nil
end
- {:ok, notifications} = Notification.create_notifications(object, do_send: false)
+ {:ok, notifications} = Notification.create_notifications(object)
meta =
meta
@@ -184,7 +183,11 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
liked_object = Object.get_by_ap_id(object.data["object"])
Utils.add_like_to_object(object, liked_object)
- Notification.create_notifications(object)
+ {:ok, notifications} = Notification.create_notifications(object)
+
+ meta =
+ meta
+ |> add_notifications(notifications)
{:ok, object, meta}
end
@@ -197,11 +200,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# - Increase replies count
# - Set up ActivityExpiration
# - Set up notifications
+ # - Index incoming posts for search (if needed)
@impl true
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
with {:ok, object, meta} <- handle_object_creation(meta[:object_data], activity, meta),
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
- {:ok, notifications} = Notification.create_notifications(activity, do_send: false)
+ {:ok, notifications} = Notification.create_notifications(activity)
{:ok, _user} = ActivityPub.increase_note_count_if_public(user, object)
{:ok, _user} = ActivityPub.update_last_status_at_if_public(user, object)
@@ -209,6 +213,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
Object.increase_replies_count(in_reply_to)
end
+ if quote_url = object.data["quoteUrl"] do
+ Object.increase_quotes_count(quote_url)
+ end
+
reply_depth = (meta[:depth] || 0) + 1
# FIXME: Force inReplyTo to replies
@@ -222,9 +230,11 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
end
- ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
- Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
- end)
+ Pleroma.Web.RichMedia.Card.get_by_activity(activity)
+
+ Pleroma.Search.add_to_index(Map.put(activity, :object, object))
+
+ Utils.maybe_handle_group_posts(activity)
meta =
meta
@@ -249,11 +259,13 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
Utils.add_announce_to_object(object, announced_object)
- if !User.is_internal_user?(user) do
- Notification.create_notifications(object)
+ {:ok, notifications} = Notification.create_notifications(object)
- ap_streamer().stream_out(object)
- end
+ if !User.internal?(user), do: ap_streamer().stream_out(object)
+
+ meta =
+ meta
+ |> add_notifications(notifications)
{:ok, object, meta}
end
@@ -274,7 +286,11 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
reacted_object = Object.get_by_ap_id(object.data["object"])
Utils.add_emoji_reaction_to_object(object, reacted_object)
- Notification.create_notifications(object)
+ {:ok, notifications} = Notification.create_notifications(object)
+
+ meta =
+ meta
+ |> add_notifications(notifications)
{:ok, object, meta}
end
@@ -285,6 +301,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# - Reduce the user note count
# - Reduce the reply count
# - Stream out the activity
+ # - Removes posts from search index (if needed)
@impl true
def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do
deleted_object =
@@ -294,9 +311,9 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
result =
case deleted_object do
%Object{} ->
- with {:ok, deleted_object, _activity} <- Object.delete(deleted_object),
+ with {_, {:ok, deleted_object, _activity}} <- {:object, Object.delete(deleted_object)},
{_, actor} when is_binary(actor) <- {:actor, deleted_object.data["actor"]},
- %User{} = user <- User.get_cached_by_ap_id(actor) do
+ {_, %User{} = user} <- {:user, User.get_cached_by_ap_id(actor)} do
User.remove_pinned_object_id(user, deleted_object.data["id"])
{:ok, user} = ActivityPub.decrease_note_count_if_public(user, deleted_object)
@@ -305,6 +322,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
Object.decrease_replies_count(in_reply_to)
end
+ if quote_url = deleted_object.data["quoteUrl"] do
+ Object.decrease_quotes_count(quote_url)
+ end
+
MessageReference.delete_for_object(deleted_object)
ap_streamer().stream_out(object)
@@ -314,6 +335,17 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
{:actor, _} ->
@logger.error("The object doesn't have an actor: #{inspect(deleted_object)}")
:no_object_actor
+
+ {:user, _} ->
+ @logger.error(
+ "The object's actor could not be resolved to a user: #{inspect(deleted_object)}"
+ )
+
+ :no_object_user
+
+ {:object, _} ->
+ @logger.error("The object could not be deleted: #{inspect(deleted_object)}")
+ {:error, object}
end
%User{} ->
@@ -323,6 +355,11 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
if result == :ok do
+ # Only remove from index when deleting actual objects, not users or anything else
+ with %Pleroma.Object{} <- deleted_object do
+ Pleroma.Search.remove_from_index(deleted_object)
+ end
+
{:ok, object, meta}
else
{:error, result}
@@ -428,37 +465,13 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
if orig_object_data["type"] in Pleroma.Constants.updatable_object_types() do
- %{
- updated_data: updated_object_data,
- updated: updated,
- used_history_in_new_object?: used_history_in_new_object?
- } = Object.Updater.make_new_object_data_from_update_object(orig_object_data, updated_object)
-
- changeset =
- orig_object
- |> Repo.preload(:hashtags)
- |> Object.change(%{data: updated_object_data})
-
- with {:ok, new_object} <- Repo.update(changeset),
- {:ok, _} <- Object.invalid_object_cache(new_object),
- {:ok, _} <- Object.set_cache(new_object),
- # The metadata/utils.ex uses the object id for the cache.
- {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(new_object.id) do
- if used_history_in_new_object? do
- with create_activity when not is_nil(create_activity) <-
- Pleroma.Activity.get_create_by_object_ap_id(orig_object_ap_id),
- {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(create_activity.id) do
- nil
- else
- _ -> nil
- end
- end
+ {:ok, _, updated} =
+ Object.Updater.do_update_and_invalidate_cache(orig_object, updated_object)
- if updated do
- object
- |> Activity.normalize()
- |> ActivityPub.notify_and_stream()
- end
+ if updated do
+ object
+ |> Activity.normalize()
+ |> ActivityPub.notify_and_stream()
end
end
@@ -520,7 +533,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
def handle_object_creation(%{"type" => objtype} = object, _activity, meta)
- when objtype in ~w[Audio Video Event Article Note Page] do
+ when objtype in ~w[Audio Video Image Event Article Note Page] do
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
{:ok, object, meta}
end
@@ -574,17 +587,14 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
def handle_undoing(object), do: {:error, ["don't know how to handle", object]}
- @spec delete_object(Object.t()) :: :ok | {:error, Ecto.Changeset.t()}
+ @spec delete_object(Activity.t()) :: :ok | {:error, Ecto.Changeset.t()}
defp delete_object(object) do
with {:ok, _} <- Repo.delete(object), do: :ok
end
defp send_notifications(meta) do
Keyword.get(meta, :notifications, [])
- |> Enum.each(fn notification ->
- Streamer.stream(["user", "user:notification"], notification)
- Push.send(notification)
- end)
+ |> Notification.send()
meta
end
diff --git a/lib/pleroma/web/activity_pub/side_effects/handling.ex b/lib/pleroma/web/activity_pub/side_effects/handling.ex
index eb012f576..4751bb4ce 100644
--- a/lib/pleroma/web/activity_pub/side_effects/handling.ex
+++ b/lib/pleroma/web/activity_pub/side_effects/handling.ex
@@ -4,5 +4,5 @@
defmodule Pleroma.Web.ActivityPub.SideEffects.Handling do
@callback handle(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
- @callback handle_after_transaction(map()) :: map()
+ @callback handle_after_transaction(keyword()) :: keyword()
end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index e4c04da0d..edfe73a25 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -20,11 +20,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Federator
- alias Pleroma.Workers.TransmogrifierWorker
import Ecto.Query
- require Logger
require Pleroma.Constants
@doc """
@@ -156,8 +154,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("context", replied_object.data["context"] || object["conversation"])
|> Map.drop(["conversation", "inReplyToAtomUri"])
else
- e ->
- Logger.warn("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
+ _ ->
object
end
else
@@ -167,6 +164,26 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_in_reply_to(object, _options), do: object
+ def fix_quote_url_and_maybe_fetch(object, options \\ []) do
+ quote_url =
+ case Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes.fix_quote_url(object) do
+ %{"quoteUrl" => quote_url} -> quote_url
+ _ -> nil
+ end
+
+ with {:quoting?, true} <- {:quoting?, not is_nil(quote_url)},
+ {:ok, quoted_object} <- get_obj_helper(quote_url, options),
+ %Activity{} <- Activity.get_create_by_object_ap_id(quoted_object.data["id"]) do
+ Map.put(object, "quoteUrl", quoted_object.data["id"])
+ else
+ {:quoting?, _} ->
+ object
+
+ _ ->
+ object
+ end
+ end
+
defp prepare_in_reply_to(in_reply_to) do
cond do
is_bitstring(in_reply_to) ->
@@ -447,7 +464,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
options
)
- when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note Page} do
+ when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note Page Image} do
fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
object =
@@ -455,6 +472,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> strip_internal_fields()
|> fix_type(fetch_options)
|> fix_in_reply_to(fetch_options)
+ |> fix_quote_url_and_maybe_fetch(fetch_options)
data = Map.put(data, "object", object)
options = Keyword.put(options, :local, false)
@@ -630,6 +648,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def set_reply_to_uri(obj), do: obj
@doc """
+ Fedibird compatibility
+ https://github.com/fedibird/mastodon/commit/dbd7ae6cf58a92ec67c512296b4daaea0d01e6ac
+ """
+ def set_quote_url(%{"quoteUrl" => quote_url} = object) when is_binary(quote_url) do
+ Map.put(object, "quoteUri", quote_url)
+ end
+
+ def set_quote_url(obj), do: obj
+
+ @doc """
Serialized Mastodon-compatible `replies` collection containing _self-replies_.
Based on Mastodon's ActivityPub::NoteSerializer#replies.
"""
@@ -683,6 +711,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> prepare_attachments
|> set_conversation
|> set_reply_to_uri
+ |> set_quote_url
|> set_replies
|> strip_internal_fields
|> strip_internal_tags
@@ -750,7 +779,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Object.normalize(fetch: false)
data =
- if Visibility.is_private?(object) && object.data["actor"] == ap_id do
+ if Visibility.private?(object) && object.data["actor"] == ap_id do
data |> Map.put("object", object |> Map.get(:data) |> prepare_object)
else
data |> maybe_fix_object_url
@@ -820,8 +849,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
relative_object do
Map.put(data, "object", external_url)
else
- {:fetch, e} ->
- Logger.error("Couldn't fetch #{object} #{inspect(e)}")
+ {:fetch, _} ->
data
_ ->
@@ -946,47 +974,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
defp strip_internal_tags(object), do: object
- def perform(:user_upgrade, user) do
- # we pass a fake user so that the followers collection is stripped away
- old_follower_address = User.ap_followers(%User{nickname: user.nickname})
-
- from(
- a in Activity,
- where: ^old_follower_address in a.recipients,
- update: [
- set: [
- recipients:
- fragment(
- "array_replace(?,?,?)",
- a.recipients,
- ^old_follower_address,
- ^user.follower_address
- )
- ]
- ]
- )
- |> Repo.update_all([])
- end
-
- def upgrade_user_from_ap_id(ap_id) do
- with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
- {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
- {:ok, user} <- update_user(user, data) do
- {:ok, _pid} = Task.start(fn -> ActivityPub.pinned_fetch_task(user) end)
- TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
- {:ok, user}
- else
- %User{} = user -> {:ok, user}
- e -> e
- end
- end
-
- defp update_user(user, data) do
- user
- |> User.remote_user_changeset(data)
- |> User.update_and_set_cache()
- end
-
def maybe_fix_user_url(%{"url" => url} = data) when is_map(url) do
Map.put(data, "url", url["href"])
end
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index b898d6fe8..797e79dda 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
alias Ecto.UUID
alias Pleroma.Activity
alias Pleroma.Config
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators.ObjectID
alias Pleroma.Maps
alias Pleroma.Notification
alias Pleroma.Object
@@ -31,7 +32,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"Page",
"Question",
"Answer",
- "Audio"
+ "Audio",
+ "Image"
]
@strip_status_report_states ~w(closed resolved)
@supported_report_states ~w(open closed resolved)
@@ -165,7 +167,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
with true <- Config.get!([:instance, :federating]),
true <- type != "Block" || outgoing_blocks,
- false <- Visibility.is_local_public?(activity) do
+ false <- Visibility.local_public?(activity) do
Pleroma.Web.Federator.publish(activity)
end
@@ -275,7 +277,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
object_actor = User.get_cached_by_ap_id(object_actor_id)
to =
- if Visibility.is_public?(object) do
+ if Visibility.public?(object) do
[actor.follower_address, object.data["actor"]]
else
[object.data["actor"]]
@@ -325,21 +327,29 @@ defmodule Pleroma.Web.ActivityPub.Utils do
{:ok, Object.t()} | {:error, Ecto.Changeset.t()}
def add_emoji_reaction_to_object(
- %Activity{data: %{"content" => emoji, "actor" => actor}},
+ %Activity{data: %{"content" => emoji, "actor" => actor}} = activity,
object
) do
reactions = get_cached_emoji_reactions(object)
+ emoji = Pleroma.Emoji.maybe_strip_name(emoji)
+ url = maybe_emoji_url(emoji, activity)
new_reactions =
- case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do
+ case Enum.find_index(reactions, fn [candidate, _, candidate_url] ->
+ if is_nil(candidate_url) do
+ emoji == candidate
+ else
+ url == candidate_url
+ end
+ end) do
nil ->
- reactions ++ [[emoji, [actor]]]
+ reactions ++ [[emoji, [actor], url]]
index ->
List.update_at(
reactions,
index,
- fn [emoji, users] -> [emoji, Enum.uniq([actor | users])] end
+ fn [emoji, users, url] -> [emoji, Enum.uniq([actor | users]), url] end
)
end
@@ -348,18 +358,40 @@ defmodule Pleroma.Web.ActivityPub.Utils do
update_element_in_object("reaction", new_reactions, object, count)
end
+ defp maybe_emoji_url(
+ name,
+ %Activity{
+ data: %{
+ "tag" => [
+ %{"type" => "Emoji", "name" => name, "icon" => %{"url" => url}}
+ ]
+ }
+ }
+ ),
+ do: url
+
+ defp maybe_emoji_url(_, _), do: nil
+
def emoji_count(reactions_list) do
- Enum.reduce(reactions_list, 0, fn [_, users], acc -> acc + length(users) end)
+ Enum.reduce(reactions_list, 0, fn [_, users, _], acc -> acc + length(users) end)
end
def remove_emoji_reaction_from_object(
- %Activity{data: %{"content" => emoji, "actor" => actor}},
+ %Activity{data: %{"content" => emoji, "actor" => actor}} = activity,
object
) do
+ emoji = Pleroma.Emoji.maybe_strip_name(emoji)
reactions = get_cached_emoji_reactions(object)
+ url = maybe_emoji_url(emoji, activity)
new_reactions =
- case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do
+ case Enum.find_index(reactions, fn [candidate, _, candidate_url] ->
+ if is_nil(candidate_url) do
+ emoji == candidate
+ else
+ url == candidate_url
+ end
+ end) do
nil ->
reactions
@@ -367,9 +399,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do
List.update_at(
reactions,
index,
- fn [emoji, users] -> [emoji, List.delete(users, actor)] end
+ fn [emoji, users, url] -> [emoji, List.delete(users, actor), url] end
)
- |> Enum.reject(fn [_, users] -> Enum.empty?(users) end)
+ |> Enum.reject(fn [_, users, _] -> Enum.empty?(users) end)
end
count = emoji_count(new_reactions)
@@ -377,11 +409,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
def get_cached_emoji_reactions(object) do
- if is_list(object.data["reactions"]) do
- object.data["reactions"]
- else
- []
- end
+ Object.get_emoji_reactions(object)
end
@spec add_like_to_object(Activity.t(), Object.t()) ::
@@ -489,17 +517,37 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do
%{data: %{"object" => object_ap_id}} = Activity.get_by_id(internal_activity_id)
+ emoji = Pleroma.Emoji.maybe_quote(emoji)
"EmojiReact"
|> Activity.Queries.by_type()
|> where(actor: ^ap_id)
- |> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji))
+ |> custom_emoji_discriminator(emoji)
|> Activity.Queries.by_object_id(object_ap_id)
|> order_by([activity], fragment("? desc nulls last", activity.id))
|> limit(1)
|> Repo.one()
end
+ defp custom_emoji_discriminator(query, emoji) do
+ if String.contains?(emoji, "@") do
+ stripped = Pleroma.Emoji.maybe_strip_name(emoji)
+ [name, domain] = String.split(stripped, "@")
+ domain_pattern = "%/" <> domain <> "/%"
+ emoji_pattern = Pleroma.Emoji.maybe_quote(name)
+
+ query
+ |> where([activity], fragment("?->>'content' = ?
+ AND EXISTS (
+ SELECT FROM jsonb_array_elements(?->'tag') elem
+ WHERE elem->>'id' ILIKE ?
+ )", activity.data, ^emoji_pattern, activity.data, ^domain_pattern))
+ else
+ query
+ |> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji))
+ end
+ end
+
#### Announce-related helpers
@doc """
@@ -673,14 +721,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do
#### Flag-related helpers
@spec make_flag_data(map(), map()) :: map()
- def make_flag_data(%{actor: actor, context: context, content: content} = params, additional) do
+ def make_flag_data(
+ %{actor: actor, context: context, content: content} = params,
+ additional
+ ) do
%{
"type" => "Flag",
"actor" => actor.ap_id,
"content" => content,
"object" => build_flag_object(params),
"context" => context,
- "state" => "open"
+ "state" => "open",
+ "rules" => Map.get(params, :rules, nil)
}
|> Map.merge(additional)
end
@@ -728,10 +780,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do
build_flag_object(object)
nil ->
- if %Object{} = object = Object.get_by_ap_id(id) do
- build_flag_object(object)
- else
- %{"id" => id, "deleted" => true}
+ case Object.get_by_ap_id(id) do
+ %Object{} = object -> build_flag_object(object)
+ _ -> %{"id" => id, "deleted" => true}
end
end
end
@@ -805,9 +856,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do
[actor | reported_activities] = activity.data["object"]
stripped_activities =
- Enum.map(reported_activities, fn
- act when is_map(act) -> act["id"]
- act when is_binary(act) -> act
+ Enum.reduce(reported_activities, [], fn act, acc ->
+ case ObjectID.cast(act) do
+ {:ok, act} -> [act | acc]
+ _ -> acc
+ end
end)
new_data = put_in(activity.data, ["object"], [actor | stripped_activities])
@@ -885,4 +938,27 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> where([a, object: o], fragment("(?)->>'type' = 'Answer'", o.data))
|> Repo.all()
end
+
+ def maybe_handle_group_posts(activity) do
+ poster = User.get_cached_by_ap_id(activity.actor)
+
+ mentions =
+ activity.data["to"]
+ |> Enum.filter(&(&1 != activity.actor))
+
+ mentioned_local_groups =
+ User.get_all_by_ap_id(mentions)
+ |> Enum.filter(fn user ->
+ user.actor_type == "Group" and
+ user.local and
+ not User.blocks?(user, poster)
+ end)
+
+ mentioned_local_groups
+ |> Enum.each(fn group ->
+ Pleroma.Web.CommonAPI.repeat(activity.id, group)
+ end)
+
+ :ok
+ end
end
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index f69fca075..937e4fd67 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -46,6 +46,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"following" => "#{user.ap_id}/following",
"followers" => "#{user.ap_id}/followers",
"inbox" => "#{user.ap_id}/inbox",
+ "outbox" => "#{user.ap_id}/outbox",
"name" => "Pleroma",
"summary" =>
"An internal service actor for this Pleroma instance. No user-serviceable parts inside.",
@@ -66,8 +67,13 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def render("user.json", %{user: %User{nickname: nil} = user}),
do: render("service.json", %{user: user})
- def render("user.json", %{user: %User{nickname: "internal." <> _} = user}),
- do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
+ def render("user.json", %{user: %User{nickname: "internal." <> _} = user}) do
+ render("service.json", %{user: user})
+ |> Map.merge(%{
+ "preferredUsername" => user.nickname,
+ "webfinger" => "acct:#{User.full_nickname(user)}"
+ })
+ end
def render("user.json", %{user: user}) do
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
@@ -120,7 +126,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"discoverable" => user.is_discoverable,
"capabilities" => capabilities,
"alsoKnownAs" => user.also_known_as,
- "vcard:bday" => birthday
+ "vcard:bday" => birthday,
+ "webfinger" => "acct:#{User.full_nickname(user)}"
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 7c57f88f9..97fc7fa1b 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -11,28 +11,28 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
require Pleroma.Constants
- @spec is_public?(Object.t() | Activity.t() | map()) :: boolean()
- def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
- def is_public?(%Object{data: data}), do: is_public?(data)
- def is_public?(%Activity{data: %{"type" => "Move"}}), do: true
- def is_public?(%Activity{data: data}), do: is_public?(data)
- def is_public?(%{"directMessage" => true}), do: false
-
- def is_public?(data) do
+ @spec public?(Object.t() | Activity.t() | map()) :: boolean()
+ def public?(%Object{data: %{"type" => "Tombstone"}}), do: false
+ def public?(%Object{data: data}), do: public?(data)
+ def public?(%Activity{data: %{"type" => "Move"}}), do: true
+ def public?(%Activity{data: data}), do: public?(data)
+ def public?(%{"directMessage" => true}), do: false
+
+ def public?(data) do
Utils.label_in_message?(Pleroma.Constants.as_public(), data) or
Utils.label_in_message?(Utils.as_local_public(), data)
end
- def is_local_public?(%Object{data: data}), do: is_local_public?(data)
- def is_local_public?(%Activity{data: data}), do: is_local_public?(data)
+ def local_public?(%Object{data: data}), do: local_public?(data)
+ def local_public?(%Activity{data: data}), do: local_public?(data)
- def is_local_public?(data) do
+ def local_public?(data) do
Utils.label_in_message?(Utils.as_local_public(), data) and
not Utils.label_in_message?(Pleroma.Constants.as_public(), data)
end
- def is_private?(activity) do
- with false <- is_public?(activity),
+ def private?(activity) do
+ with false <- public?(activity),
%User{follower_address: follower_address} <-
User.get_cached_by_ap_id(activity.data["actor"]) do
follower_address in activity.data["to"]
@@ -41,20 +41,20 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
end
end
- def is_announceable?(activity, user, public \\ true) do
- is_public?(activity) ||
- (!public && is_private?(activity) && activity.data["actor"] == user.ap_id)
+ def announceable?(activity, user, public \\ true) do
+ public?(activity) ||
+ (!public && private?(activity) && activity.data["actor"] == user.ap_id)
end
- def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
- def is_direct?(%Object{data: %{"directMessage" => true}}), do: true
+ def direct?(%Activity{data: %{"directMessage" => true}}), do: true
+ def direct?(%Object{data: %{"directMessage" => true}}), do: true
- def is_direct?(activity) do
- !is_public?(activity) && !is_private?(activity)
+ def direct?(activity) do
+ !public?(activity) && !private?(activity)
end
- def is_list?(%{data: %{"listMessage" => _}}), do: true
- def is_list?(_), do: false
+ def list?(%{data: %{"listMessage" => _}}), do: true
+ def list?(_), do: false
@spec visible_for_user?(Object.t() | Activity.t() | nil, User.t() | nil) :: boolean()
def visible_for_user?(%Object{data: %{"type" => "Tombstone"}}, _), do: false
@@ -77,7 +77,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
when module in [Activity, Object] do
if restrict_unauthenticated_access?(message),
do: false,
- else: is_public?(message) and not is_local_public?(message)
+ else: public?(message) and not local_public?(message)
end
def visible_for_user?(%{__struct__: module} = message, user)
@@ -86,8 +86,8 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
y = [message.data["actor"]] ++ message.data["to"] ++ (message.data["cc"] || [])
user_is_local = user.local
- federatable = not is_local_public?(message)
- (is_public?(message) || Enum.any?(x, &(&1 in y))) and (user_is_local || federatable)
+ federatable = not local_public?(message)
+ (public?(message) || Enum.any?(x, &(&1 in y))) and (user_is_local || federatable)
end
def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do