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.ex27
-rw-r--r--lib/pleroma/web/activity_pub/mrf/dnsrbl_policy.ex146
-rw-r--r--lib/pleroma/web/activity_pub/mrf/fo_direct_reply.ex53
-rw-r--r--lib/pleroma/web/activity_pub/mrf/quiet_reply.ex60
-rw-r--r--lib/pleroma/web/activity_pub/object_validator.ex12
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex5
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex1
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_fields.ex2
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_fixes.ex68
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/event_validator.ex6
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/question_validator.ex1
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex210
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex11
-rw-r--r--lib/pleroma/web/activity_pub/views/object_view.ex6
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex3
15 files changed, 258 insertions, 353 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index df8795fe4..62c7a7b31 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -924,6 +924,31 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
)
end
+ # Essentially, either look for activities addressed to `recipients`, _OR_ ones
+ # that reference a hashtag that the user follows
+ # Firstly, two fallbacks in case there's no hashtag constraint, or the user doesn't
+ # follow any
+ defp restrict_recipients_or_hashtags(query, recipients, user, nil) do
+ restrict_recipients(query, recipients, user)
+ end
+
+ defp restrict_recipients_or_hashtags(query, recipients, user, []) do
+ restrict_recipients(query, recipients, user)
+ end
+
+ defp restrict_recipients_or_hashtags(query, recipients, _user, hashtag_ids) do
+ from([activity, object] in query)
+ |> join(:left, [activity, object], hto in "hashtags_objects",
+ on: hto.object_id == object.id,
+ as: :hto
+ )
+ |> where(
+ [activity, object, hto: hto],
+ (hto.hashtag_id in ^hashtag_ids and ^Constants.as_public() in activity.recipients) or
+ fragment("? && ?", ^recipients, activity.recipients)
+ )
+ end
+
defp restrict_local(query, %{local_only: true}) do
from(activity in query, where: activity.local == true)
end
@@ -1414,7 +1439,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> maybe_preload_report_notes(opts)
|> maybe_set_thread_muted_field(opts)
|> maybe_order(opts)
- |> restrict_recipients(recipients, opts[:user])
+ |> restrict_recipients_or_hashtags(recipients, opts[:user], opts[:followed_hashtags])
|> restrict_replies(opts)
|> restrict_since(opts)
|> restrict_local(opts)
diff --git a/lib/pleroma/web/activity_pub/mrf/dnsrbl_policy.ex b/lib/pleroma/web/activity_pub/mrf/dnsrbl_policy.ex
deleted file mode 100644
index ca41c464c..000000000
--- a/lib/pleroma/web/activity_pub/mrf/dnsrbl_policy.ex
+++ /dev/null
@@ -1,146 +0,0 @@
-# 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.DNSRBLPolicy do
- @moduledoc """
- Dynamic activity filtering based on an RBL database
-
- This MRF makes queries to a custom DNS server which will
- respond with values indicating the classification of the domain
- the activity originated from. This method has been widely used
- in the email anti-spam industry for very fast reputation checks.
-
- e.g., if the DNS response is 127.0.0.1 or empty, the domain is OK
- Other values such as 127.0.0.2 may be used for specific classifications.
-
- Information for why the host is blocked can be stored in a corresponding TXT record.
-
- This method is fail-open so if the queries fail the activites are accepted.
-
- An example of software meant for this purpsoe is rbldnsd which can be found
- at http://www.corpit.ru/mjt/rbldnsd.html or mirrored at
- https://git.pleroma.social/feld/rbldnsd
-
- It is highly recommended that you run your own copy of rbldnsd and use an
- external mechanism to sync/share the contents of the zone file. This is
- important to keep the latency on the queries as low as possible and prevent
- your DNS server from being attacked so it fails and content is permitted.
- """
-
- @behaviour Pleroma.Web.ActivityPub.MRF.Policy
-
- alias Pleroma.Config
-
- require Logger
-
- @query_retries 1
- @query_timeout 500
-
- @impl true
- def filter(%{"actor" => actor} = activity) do
- actor_info = URI.parse(actor)
-
- with {:ok, activity} <- check_rbl(actor_info, activity) do
- {:ok, activity}
- else
- _ -> {:reject, "[DNSRBLPolicy]"}
- end
- end
-
- @impl true
- def filter(activity), do: {:ok, activity}
-
- @impl true
- def describe do
- mrf_dnsrbl =
- Config.get(:mrf_dnsrbl)
- |> Enum.into(%{})
-
- {:ok, %{mrf_dnsrbl: mrf_dnsrbl}}
- end
-
- @impl true
- def config_description do
- %{
- key: :mrf_dnsrbl,
- related_policy: "Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy",
- label: "MRF DNSRBL",
- description: "DNS RealTime Blackhole Policy",
- children: [
- %{
- key: :nameserver,
- type: {:string},
- description: "DNSRBL Nameserver to Query (IP or hostame)",
- suggestions: ["127.0.0.1"]
- },
- %{
- key: :port,
- type: {:string},
- description: "Nameserver port",
- suggestions: ["53"]
- },
- %{
- key: :zone,
- type: {:string},
- description: "Root zone for querying",
- suggestions: ["bl.pleroma.com"]
- }
- ]
- }
- end
-
- defp check_rbl(%{host: actor_host}, activity) do
- with false <- match?(^actor_host, Pleroma.Web.Endpoint.host()),
- zone when not is_nil(zone) <- Keyword.get(Config.get([:mrf_dnsrbl]), :zone) do
- query =
- Enum.join([actor_host, zone], ".")
- |> String.to_charlist()
-
- rbl_response = rblquery(query)
-
- if Enum.empty?(rbl_response) do
- {:ok, activity}
- else
- Task.start(fn ->
- reason =
- case rblquery(query, :txt) do
- [[result]] -> result
- _ -> "undefined"
- end
-
- Logger.warning(
- "DNSRBL Rejected activity from #{actor_host} for reason: #{inspect(reason)}"
- )
- end)
-
- :error
- end
- else
- _ -> {:ok, activity}
- end
- end
-
- defp get_rblhost_ip(rblhost) do
- case rblhost |> String.to_charlist() |> :inet_parse.address() do
- {:ok, _} -> rblhost |> String.to_charlist() |> :inet_parse.address()
- _ -> {:ok, rblhost |> String.to_charlist() |> :inet_res.lookup(:in, :a) |> Enum.random()}
- end
- end
-
- defp rblquery(query, type \\ :a) do
- config = Config.get([:mrf_dnsrbl])
-
- case get_rblhost_ip(config[:nameserver]) do
- {:ok, rblnsip} ->
- :inet_res.lookup(query, :in, type,
- nameservers: [{rblnsip, config[:port]}],
- timeout: @query_timeout,
- retry: @query_retries
- )
-
- _ ->
- []
- end
- end
-end
diff --git a/lib/pleroma/web/activity_pub/mrf/fo_direct_reply.ex b/lib/pleroma/web/activity_pub/mrf/fo_direct_reply.ex
deleted file mode 100644
index 2cf22745a..000000000
--- a/lib/pleroma/web/activity_pub/mrf/fo_direct_reply.ex
+++ /dev/null
@@ -1,53 +0,0 @@
-# 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.FODirectReply do
- @moduledoc """
- FODirectReply alters the scope of replies to activities which are Followers Only to be Direct. The purpose of this policy is to prevent broken threads for followers of the reply author because their response was to a user that they are not also following.
- """
-
- alias Pleroma.Object
- alias Pleroma.User
- alias Pleroma.Web.ActivityPub.Visibility
-
- @behaviour Pleroma.Web.ActivityPub.MRF.Policy
-
- @impl true
- def filter(
- %{
- "type" => "Create",
- "to" => to,
- "object" => %{
- "actor" => actor,
- "type" => "Note",
- "inReplyTo" => in_reply_to
- }
- } = activity
- ) do
- with true <- is_binary(in_reply_to),
- %User{follower_address: followers_collection, local: true} <- User.get_by_ap_id(actor),
- %Object{} = in_reply_to_object <- Object.get_by_ap_id(in_reply_to),
- "private" <- Visibility.get_visibility(in_reply_to_object) do
- direct_to = to -- [followers_collection]
-
- updated_activity =
- activity
- |> Map.put("cc", [])
- |> Map.put("to", direct_to)
- |> Map.put("directMessage", true)
- |> put_in(["object", "cc"], [])
- |> put_in(["object", "to"], direct_to)
-
- {:ok, updated_activity}
- else
- _ -> {:ok, activity}
- end
- end
-
- @impl true
- def filter(activity), do: {:ok, activity}
-
- @impl true
- def describe, do: {:ok, %{}}
-end
diff --git a/lib/pleroma/web/activity_pub/mrf/quiet_reply.ex b/lib/pleroma/web/activity_pub/mrf/quiet_reply.ex
deleted file mode 100644
index b07dc3b56..000000000
--- a/lib/pleroma/web/activity_pub/mrf/quiet_reply.ex
+++ /dev/null
@@ -1,60 +0,0 @@
-# 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.QuietReply do
- @moduledoc """
- QuietReply alters the scope of activities from local users when replying by enforcing them to be "Unlisted" or "Quiet Public". This delivers the activity to all the expected recipients and instances, but it will not be published in the Federated / The Whole Known Network timelines. It will still be published to the Home timelines of the user's followers and visible to anyone who opens the thread.
- """
- require Pleroma.Constants
-
- alias Pleroma.User
-
- @behaviour Pleroma.Web.ActivityPub.MRF.Policy
-
- @impl true
- def history_awareness, do: :auto
-
- @impl true
- def filter(
- %{
- "type" => "Create",
- "to" => to,
- "cc" => cc,
- "object" => %{
- "actor" => actor,
- "type" => "Note",
- "inReplyTo" => in_reply_to
- }
- } = activity
- ) do
- with true <- is_binary(in_reply_to),
- false <- match?([], cc),
- %User{follower_address: followers_collection, local: true} <-
- User.get_by_ap_id(actor) do
- updated_to =
- to
- |> Kernel.++([followers_collection])
- |> Kernel.--([Pleroma.Constants.as_public()])
-
- updated_cc = [Pleroma.Constants.as_public()]
-
- updated_activity =
- activity
- |> Map.put("to", updated_to)
- |> Map.put("cc", updated_cc)
- |> put_in(["object", "to"], updated_to)
- |> put_in(["object", "cc"], updated_cc)
-
- {:ok, updated_activity}
- else
- _ -> {:ok, activity}
- end
- end
-
- @impl true
- def filter(activity), do: {:ok, activity}
-
- @impl true
- def describe, do: {:ok, %{}}
-end
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index c509890f6..ee12f3ebf 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -26,6 +26,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
@@ -115,7 +116,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
meta
)
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),
+ with {:ok, object_data} <-
+ object
+ |> CommonFixes.maybe_add_language_from_activity(create_activity)
+ |> cast_and_apply_and_stringify_with_history(),
meta = Keyword.put(meta, :object_data, object_data),
{:ok, create_activity} <-
create_activity
@@ -165,7 +169,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
)
when objtype in ~w[Question Answer Audio Video Event Article Note Page] do
with {_, false} <- {:local, Access.get(meta, :local, false)},
- {_, {:ok, object_data, _}} <- {:object_validation, validate(object, meta)},
+ {_, {:ok, object_data, _}} <-
+ {:object_validation,
+ object
+ |> CommonFixes.maybe_add_language_from_activity(update_activity)
+ |> validate(meta)},
meta = Keyword.put(meta, :object_data, object_data),
{:ok, update_activity} <-
update_activity
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 1b5b2e8fb..81ab354fe 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
@@ -30,7 +30,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
def cast_and_apply(data) do
data
- |> cast_data
+ |> cast_data()
|> apply_action(:insert)
end
@@ -85,8 +85,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|> fix_replies()
|> fix_attachments()
|> CommonFixes.fix_quote_url()
+ |> CommonFixes.fix_likes()
|> Transmogrifier.fix_emoji()
|> Transmogrifier.fix_content_map()
+ |> CommonFixes.maybe_add_language()
+ |> CommonFixes.maybe_add_content_map()
end
def changeset(struct, data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex
index 65ac6bb93..034c6f33f 100644
--- a/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex
@@ -100,6 +100,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator do
|> CommonFixes.fix_actor()
|> CommonFixes.fix_object_defaults()
|> CommonFixes.fix_quote_url()
+ |> CommonFixes.fix_likes()
|> Transmogrifier.fix_emoji()
|> fix_url()
|> fix_content()
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 1a5d02601..22cf0cc05 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex
@@ -31,6 +31,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
defmacro object_fields do
quote bind_quoted: binding() do
field(:content, :string)
+ field(:contentMap, ObjectValidators.ContentLanguageMap)
field(:published, ObjectValidators.DateTime)
field(:updated, ObjectValidators.DateTime)
@@ -58,6 +59,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:quotes_count, :integer, default: 0)
+ field(:language, ObjectValidators.LanguageCode)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:quoteUrl, ObjectValidators.ObjectID)
field(:url, ObjectValidators.BareUri)
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 4699029d4..87d3e0c8f 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
@@ -11,6 +11,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
+ import Pleroma.EctoType.ActivityPub.ObjectValidators.LanguageCode,
+ only: [good_locale_code?: 1]
+
+ import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
+
require Pleroma.Constants
def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do
@@ -114,6 +119,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
def fix_quote_url(data), do: data
+ # On Mastodon, `"likes"` attribute includes an inlined `Collection` with `totalItems`,
+ # not a list of users.
+ # https://github.com/mastodon/mastodon/pull/32007
+ def fix_likes(%{"likes" => %{}} = data), do: Map.drop(data, ["likes"])
+
+ def fix_likes(data), do: data
+
# https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md
def object_link_tag?(%{
"type" => "Link",
@@ -125,4 +137,60 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
end
def object_link_tag?(_), do: false
+
+ def maybe_add_language_from_activity(object, activity) do
+ language = get_language_from_context(activity)
+
+ if language do
+ Map.put(object, "language", language)
+ else
+ object
+ end
+ end
+
+ def maybe_add_language(object) do
+ language =
+ [
+ get_language_from_context(object),
+ get_language_from_content_map(object)
+ ]
+ |> Enum.find(&good_locale_code?(&1))
+
+ if language do
+ Map.put(object, "language", language)
+ else
+ object
+ end
+ end
+
+ defp get_language_from_context(%{"@context" => context}) when is_list(context) do
+ case context
+ |> Enum.find(fn
+ %{"@language" => language} -> language != "und"
+ _ -> nil
+ end) do
+ %{"@language" => language} -> language
+ _ -> nil
+ end
+ end
+
+ defp get_language_from_context(_), do: nil
+
+ defp get_language_from_content_map(%{"contentMap" => content_map, "content" => source_content}) do
+ content_groups = Map.to_list(content_map)
+
+ case Enum.find(content_groups, fn {_, content} -> content == source_content end) do
+ {language, _} -> language
+ _ -> nil
+ end
+ end
+
+ defp get_language_from_content_map(_), do: nil
+
+ def maybe_add_content_map(%{"language" => language, "content" => content} = object)
+ when not_empty_string(language) do
+ Map.put(object, "contentMap", Map.put(%{}, language, content))
+ end
+
+ def maybe_add_content_map(object), do: object
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
index ab204f69a..ea14d6aca 100644
--- a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
@@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
def cast_and_apply(data) do
data
- |> cast_data
+ |> cast_data()
|> apply_action(:insert)
end
@@ -38,6 +38,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
|> validate_data()
end
+ @spec cast_data(map()) :: map()
def cast_data(data) do
%__MODULE__{}
|> changeset(data)
@@ -47,7 +48,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
data
|> CommonFixes.fix_actor()
|> CommonFixes.fix_object_defaults()
+ |> CommonFixes.fix_likes()
|> Transmogrifier.fix_emoji()
+ |> CommonFixes.maybe_add_language()
+ |> CommonFixes.maybe_add_content_map()
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 7f9d4d648..21940f4f1 100644
--- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
@@ -64,6 +64,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|> CommonFixes.fix_actor()
|> CommonFixes.fix_object_defaults()
|> CommonFixes.fix_quote_url()
+ |> CommonFixes.fix_likes()
|> Transmogrifier.fix_emoji()
|> fix_closed()
end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 2f8a7f8f2..1e6ee7dc8 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -16,12 +16,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Federator
import Ecto.Query
+ import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
require Pleroma.Constants
@@ -41,6 +43,38 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> fix_content_map()
|> fix_addressing()
|> fix_summary()
+ |> fix_history(&fix_object/1)
+ end
+
+ defp maybe_fix_object(%{"attributedTo" => _} = object), do: fix_object(object)
+ defp maybe_fix_object(object), do: object
+
+ defp fix_history(%{"formerRepresentations" => %{"orderedItems" => list}} = obj, fix_fun)
+ when is_list(list) do
+ update_in(obj["formerRepresentations"]["orderedItems"], fn h -> Enum.map(h, fix_fun) end)
+ end
+
+ defp fix_history(obj, _), do: obj
+
+ defp fix_recursive(obj, fun) do
+ # unlike Erlang, Elixir does not support recursive inline functions
+ # which would allow us to avoid reconstructing this on every recursion
+ rec_fun = fn
+ obj when is_map(obj) -> fix_recursive(obj, fun)
+ # there may be simple AP IDs in history (or object field)
+ obj -> obj
+ end
+
+ obj
+ |> fun.()
+ |> fix_history(rec_fun)
+ |> then(fn
+ %{"object" => object} = doc when is_map(object) ->
+ update_in(doc["object"], rec_fun)
+
+ apdoc ->
+ apdoc
+ end)
end
def fix_summary(%{"summary" => nil} = object) do
@@ -166,7 +200,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_quote_url_and_maybe_fetch(object, options \\ []) do
quote_url =
- case Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes.fix_quote_url(object) do
+ case CommonFixes.fix_quote_url(object) do
%{"quoteUrl" => quote_url} -> quote_url
_ -> nil
end
@@ -336,6 +370,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_tag(object), do: object
+ # prefer content over contentMap
+ def fix_content_map(%{"content" => content} = object) when not_empty_string(content), do: object
+
# content map usually only has one language so this will do for now.
def fix_content_map(%{"contentMap" => content_map} = object) do
content_groups = Map.to_list(content_map)
@@ -370,11 +407,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end)
end
- def handle_incoming(data, options \\ [])
+ def handle_incoming(data, options \\ []) do
+ data
+ |> fix_recursive(&strip_internal_fields/1)
+ |> handle_incoming_normalized(options)
+ end
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
# with nil ID.
- def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data, _options) do
+ defp handle_incoming_normalized(
+ %{"type" => "Flag", "object" => objects, "actor" => actor} = data,
+ _options
+ ) do
with context <- data["context"] || Utils.generate_context_id(),
content <- data["content"] || "",
%User{} = actor <- User.get_cached_by_ap_id(actor),
@@ -395,16 +439,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
# disallow objects with bogus IDs
- def handle_incoming(%{"id" => nil}, _options), do: :error
- def handle_incoming(%{"id" => ""}, _options), do: :error
+ defp handle_incoming_normalized(%{"id" => nil}, _options), do: :error
+ defp handle_incoming_normalized(%{"id" => ""}, _options), do: :error
# length of https:// = 8, should validate better, but good enough for now.
- def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8,
- do: :error
-
- def handle_incoming(
- %{"type" => "Listen", "object" => %{"type" => "Audio"} = object} = data,
- options
- ) do
+ defp handle_incoming_normalized(%{"id" => id}, _options)
+ when is_binary(id) and byte_size(id) < 8,
+ do: :error
+
+ defp handle_incoming_normalized(
+ %{"type" => "Listen", "object" => %{"type" => "Audio"} = object} = data,
+ options
+ ) do
actor = Containment.get_actor(data)
data =
@@ -446,25 +491,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"star" => "⭐"
}
- @doc "Rewrite misskey likes into EmojiReacts"
- def handle_incoming(
- %{
- "type" => "Like",
- "_misskey_reaction" => reaction
- } = data,
- options
- ) do
+ # Rewrite misskey likes into EmojiReacts
+ defp handle_incoming_normalized(
+ %{
+ "type" => "Like",
+ "_misskey_reaction" => reaction
+ } = data,
+ options
+ ) do
data
|> Map.put("type", "EmojiReact")
|> Map.put("content", @misskey_reactions[reaction] || reaction)
- |> handle_incoming(options)
+ |> handle_incoming_normalized(options)
end
- def handle_incoming(
- %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
- options
- )
- when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note Page Image} do
+ defp handle_incoming_normalized(
+ %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
+ options
+ )
+ 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 =
@@ -487,8 +532,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(%{"type" => type} = data, _options)
- when type in ~w{Like EmojiReact Announce Add Remove} do
+ defp handle_incoming_normalized(%{"type" => type} = data, _options)
+ when type in ~w{Like EmojiReact Announce Add Remove} do
with :ok <- ObjectValidator.fetch_actor_and_object(data),
{:ok, activity, _meta} <-
Pipeline.common_pipeline(data, local: false) do
@@ -498,11 +543,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(
- %{"type" => type} = data,
- _options
- )
- when type in ~w{Update Block Follow Accept Reject} do
+ defp handle_incoming_normalized(
+ %{"type" => type} = data,
+ _options
+ )
+ when type in ~w{Update Block Follow Accept Reject} do
+ fixed_obj = maybe_fix_object(data["object"])
+ data = if fixed_obj != nil, do: %{data | "object" => fixed_obj}, else: data
+
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{:ok, activity, _} <-
Pipeline.common_pipeline(data, local: false) do
@@ -510,10 +558,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(
- %{"type" => "Delete"} = data,
- _options
- ) do
+ defp handle_incoming_normalized(
+ %{"type" => "Delete"} = data,
+ _options
+ ) do
with {:ok, activity, _} <-
Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
@@ -536,15 +584,15 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(
- %{
- "type" => "Undo",
- "object" => %{"type" => "Follow", "object" => followed},
- "actor" => follower,
- "id" => id
- } = _data,
- _options
- ) do
+ defp handle_incoming_normalized(
+ %{
+ "type" => "Undo",
+ "object" => %{"type" => "Follow", "object" => followed},
+ "actor" => follower,
+ "id" => id
+ } = _data,
+ _options
+ ) do
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
{:ok, activity} <- ActivityPub.unfollow(follower, followed, id, false) do
@@ -555,46 +603,46 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(
- %{
- "type" => "Undo",
- "object" => %{"type" => type}
- } = data,
- _options
- )
- when type in ["Like", "EmojiReact", "Announce", "Block"] do
+ defp handle_incoming_normalized(
+ %{
+ "type" => "Undo",
+ "object" => %{"type" => type}
+ } = data,
+ _options
+ )
+ when type in ["Like", "EmojiReact", "Announce", "Block"] do
with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
end
end
# For Undos that don't have the complete object attached, try to find it in our database.
- def handle_incoming(
- %{
- "type" => "Undo",
- "object" => object
- } = activity,
- options
- )
- when is_binary(object) do
+ defp handle_incoming_normalized(
+ %{
+ "type" => "Undo",
+ "object" => object
+ } = activity,
+ options
+ )
+ when is_binary(object) do
with %Activity{data: data} <- Activity.get_by_ap_id(object) do
activity
|> Map.put("object", data)
- |> handle_incoming(options)
+ |> handle_incoming_normalized(options)
else
_e -> :error
end
end
- def handle_incoming(
- %{
- "type" => "Move",
- "actor" => origin_actor,
- "object" => origin_actor,
- "target" => target_actor
- },
- _options
- ) do
+ defp handle_incoming_normalized(
+ %{
+ "type" => "Move",
+ "actor" => origin_actor,
+ "object" => origin_actor,
+ "target" => target_actor
+ },
+ _options
+ ) do
with %User{} = origin_user <- User.get_cached_by_ap_id(origin_actor),
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_actor),
true <- origin_actor in target_user.also_known_as do
@@ -604,7 +652,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(_, _), do: :error
+ defp handle_incoming_normalized(_, _), do: :error
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
def get_obj_helper(id, options \\ []) do
@@ -716,6 +764,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> set_reply_to_uri
|> set_quote_url
|> set_replies
+ |> CommonFixes.maybe_add_content_map()
|> strip_internal_fields
|> strip_internal_tags
|> set_type
@@ -750,12 +799,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
object_id
|> Object.normalize(fetch: false)
|> Map.get(:data)
- |> prepare_object
data =
data
- |> Map.put("object", object)
- |> Map.merge(Utils.make_json_ld_header())
+ |> Map.put("object", prepare_object(object))
+ |> Map.merge(Utils.make_json_ld_header(object))
|> Map.delete("bcc")
{:ok, data}
@@ -763,14 +811,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def prepare_outgoing(%{"type" => "Update", "object" => %{"type" => objtype} = object} = data)
when objtype in Pleroma.Constants.updatable_object_types() do
- object =
- object
- |> prepare_object
-
data =
data
- |> Map.put("object", object)
- |> Map.merge(Utils.make_json_ld_header())
+ |> Map.put("object", prepare_object(object))
+ |> Map.merge(Utils.make_json_ld_header(object))
|> Map.delete("bcc")
{:ok, data}
@@ -840,7 +884,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
data
|> strip_internal_fields
|> maybe_fix_object_url
- |> Map.merge(Utils.make_json_ld_header())
+ |> Map.merge(Utils.make_json_ld_header(data))
{:ok, data}
end
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 6c792804d..f30c92abf 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -20,6 +20,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
alias Pleroma.Web.Router.Helpers
import Ecto.Query
+ import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
require Logger
require Pleroma.Constants
@@ -109,18 +110,24 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
end
- def make_json_ld_header do
+ def make_json_ld_header(data \\ %{}) do
%{
"@context" => [
"https://www.w3.org/ns/activitystreams",
"#{Endpoint.url()}/schemas/litepub-0.1.jsonld",
%{
- "@language" => "und"
+ "@language" => get_language(data)
}
]
}
end
+ defp get_language(%{"language" => language}) when not_empty_string(language) do
+ language
+ end
+
+ defp get_language(_), do: "und"
+
def make_date do
DateTime.utc_now() |> DateTime.to_iso8601()
end
diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex
index 63caa915c..13b5b2542 100644
--- a/lib/pleroma/web/activity_pub/views/object_view.ex
+++ b/lib/pleroma/web/activity_pub/views/object_view.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
alias Pleroma.Web.ActivityPub.Transmogrifier
def render("object.json", %{object: %Object{} = object}) do
- base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
+ base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header(object.data)
additional = Transmogrifier.prepare_object(object.data)
Map.merge(base, additional)
@@ -17,7 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
def render("object.json", %{object: %Activity{data: %{"type" => activity_type}} = activity})
when activity_type in ["Create", "Listen"] do
- base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
+ base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header(activity.data)
object = Object.normalize(activity, fetch: false)
additional =
@@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
end
def render("object.json", %{object: %Activity{} = activity}) do
- base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
+ base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header(activity.data)
object_id = Object.normalize(activity, id_only: true)
additional =
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index cd485ed64..61975387b 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -127,7 +127,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"capabilities" => capabilities,
"alsoKnownAs" => user.also_known_as,
"vcard:bday" => birthday,
- "webfinger" => "acct:#{User.full_nickname(user)}"
+ "webfinger" => "acct:#{User.full_nickname(user)}",
+ "published" => Pleroma.Web.CommonAPI.Utils.to_masto_date(user.inserted_at)
}
|> Map.merge(
maybe_make_image(