aboutsummaryrefslogtreecommitdiff
path: root/lib/pleroma/web
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pleroma/web')
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex36
-rw-r--r--lib/pleroma/web/activity_pub/builder.ex43
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex14
-rw-r--r--lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex2
-rw-r--r--lib/pleroma/web/activity_pub/object_validator.ex37
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_validations.ex32
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/create_validator.ex30
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/like_validator.ex57
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/note_validator.ex63
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/types/date_time.ex34
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/types/object_id.ex33
-rw-r--r--lib/pleroma/web/activity_pub/pipeline.ex42
-rw-r--r--lib/pleroma/web/activity_pub/side_effects.ex28
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex78
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex96
-rw-r--r--lib/pleroma/web/admin_api/admin_api_controller.ex8
-rw-r--r--lib/pleroma/web/admin_api/views/report_view.ex28
-rw-r--r--lib/pleroma/web/common_api/common_api.ex60
-rw-r--r--lib/pleroma/web/controller_helper.ex7
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex10
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/status_controller.ex6
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex133
-rw-r--r--lib/pleroma/web/mastodon_api/views/notification_view.ex111
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex99
-rw-r--r--lib/pleroma/web/metadata.ex7
-rw-r--r--lib/pleroma/web/metadata/opengraph.ex2
-rw-r--r--lib/pleroma/web/metadata/restrict_indexing.ex25
-rw-r--r--lib/pleroma/web/rel_me.ex18
-rw-r--r--lib/pleroma/web/rich_media/parser.ex18
-rw-r--r--lib/pleroma/web/router.ex1
-rw-r--r--lib/pleroma/web/streamer/worker.ex2
-rw-r--r--lib/pleroma/web/templates/static_fe/static_fe/_user_card.html.eex2
-rw-r--r--lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex2
-rw-r--r--lib/pleroma/web/web_finger/web_finger.ex5
35 files changed, 924 insertions, 247 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 974231925..19286fd01 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -125,6 +125,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def increase_poll_votes_if_vote(_create_data), do: :noop
+ @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
+ def persist(object, meta) do
+ with local <- Keyword.fetch!(meta, :local),
+ {recipients, _, _} <- get_recipients(object),
+ {:ok, activity} <-
+ Repo.insert(%Activity{
+ data: object,
+ local: local,
+ recipients: recipients,
+ actor: object["actor"]
+ }) do
+ {:ok, activity, meta}
+ end
+ end
+
@spec insert(map(), boolean(), boolean(), boolean()) :: {:ok, Activity.t()} | {:error, any()}
def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do
with nil <- Activity.normalize(map),
@@ -1239,17 +1254,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp fetch_activities_query_ap_ids_ops(opts) do
source_user = opts["muting_user"]
- ap_id_relations = if source_user, do: [:mute, :reblog_mute], else: []
+ ap_id_relationships = if source_user, do: [:mute, :reblog_mute], else: []
- ap_id_relations =
- ap_id_relations ++
+ ap_id_relationships =
+ ap_id_relationships ++
if opts["blocking_user"] && opts["blocking_user"] == source_user do
[:block]
else
[]
end
- preloaded_ap_ids = User.outgoing_relations_ap_ids(source_user, ap_id_relations)
+ preloaded_ap_ids = User.outgoing_relationships_ap_ids(source_user, ap_id_relationships)
restrict_blocked_opts = Map.merge(%{"blocked_users_ap_ids" => preloaded_ap_ids[:block]}, opts)
restrict_muted_opts = Map.merge(%{"muted_users_ap_ids" => preloaded_ap_ids[:mute]}, opts)
@@ -1379,6 +1394,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
+ @spec get_actor_url(any()) :: binary() | nil
+ defp get_actor_url(url) when is_binary(url), do: url
+ defp get_actor_url(%{"href" => href}) when is_binary(href), do: href
+
+ defp get_actor_url(url) when is_list(url) do
+ url
+ |> List.first()
+ |> get_actor_url()
+ end
+
+ defp get_actor_url(_url), do: nil
+
defp object_to_user_data(data) do
avatar =
data["icon"]["url"] &&
@@ -1408,6 +1435,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
user_data = %{
ap_id: data["id"],
+ uri: get_actor_url(data["url"]),
ap_enabled: true,
source_data: data,
banner: banner,
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
new file mode 100644
index 000000000..429a510b8
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -0,0 +1,43 @@
+defmodule Pleroma.Web.ActivityPub.Builder do
+ @moduledoc """
+ This module builds the objects. Meant to be used for creating local objects.
+
+ This module encodes our addressing policies and general shape of our objects.
+ """
+
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.ActivityPub.Visibility
+
+ @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
+ def like(actor, object) do
+ object_actor = User.get_cached_by_ap_id(object.data["actor"])
+
+ # Address the actor of the object, and our actor's follower collection if the post is public.
+ to =
+ if Visibility.is_public?(object) do
+ [actor.follower_address, object.data["actor"]]
+ else
+ [object.data["actor"]]
+ end
+
+ # CC everyone who's been addressed in the object, except ourself and the object actor's
+ # follower collection
+ cc =
+ (object.data["to"] ++ (object.data["cc"] || []))
+ |> List.delete(actor.ap_id)
+ |> List.delete(object_actor.follower_address)
+
+ {:ok,
+ %{
+ "id" => Utils.generate_activity_id(),
+ "actor" => actor.ap_id,
+ "type" => "Like",
+ "object" => object.data["id"],
+ "to" => to,
+ "cc" => cc,
+ "context" => object.data["context"]
+ }, []}
+ end
+end
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 b3547ecd4..0270b96ae 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
index d9a0acfd3..dfab105a3 100644
--- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
@@ -12,17 +12,23 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
require Logger
- @hackney_options [
- pool: :media,
- recv_timeout: 10_000
+ @options [
+ pool: :media
]
def perform(:prefetch, url) do
Logger.debug("Prefetching #{inspect(url)}")
+ opts =
+ if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
+ Keyword.put(@options, :recv_timeout, 10_000)
+ else
+ @options
+ end
+
url
|> MediaProxy.url()
- |> HTTP.get([], adapter: @hackney_options)
+ |> HTTP.get([], adapter: opts)
end
def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do
diff --git a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
index f67f48ab6..fc3475048 100644
--- a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
new file mode 100644
index 000000000..dc4bce059
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -0,0 +1,37 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidator do
+ @moduledoc """
+ This module is responsible for validating an object (which can be an activity)
+ and checking if it is both well formed and also compatible with our view of
+ the system.
+ """
+
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+
+ @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
+ def validate(object, meta)
+
+ def validate(%{"type" => "Like"} = object, meta) do
+ with {:ok, object} <-
+ object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object |> Map.from_struct())
+ {:ok, object, meta}
+ end
+ end
+
+ def stringify_keys(object) do
+ object
+ |> Map.new(fn {key, val} -> {to_string(key), val} end)
+ end
+
+ def fetch_actor_and_object(object) do
+ User.get_or_fetch_by_ap_id(object["actor"])
+ Object.normalize(object["object"])
+ :ok
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
new file mode 100644
index 000000000..b479c3918
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
@@ -0,0 +1,32 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
+ import Ecto.Changeset
+
+ alias Pleroma.Object
+ alias Pleroma.User
+
+ def validate_actor_presence(cng, field_name \\ :actor) do
+ cng
+ |> validate_change(field_name, fn field_name, actor ->
+ if User.get_cached_by_ap_id(actor) do
+ []
+ else
+ [{field_name, "can't find user"}]
+ end
+ end)
+ end
+
+ def validate_object_presence(cng, field_name \\ :object) do
+ cng
+ |> validate_change(field_name, fn field_name, object ->
+ if Object.get_cached_by_ap_id(object) do
+ []
+ else
+ [{field_name, "can't find object"}]
+ end
+ end)
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_validator.ex
new file mode 100644
index 000000000..926804ce7
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/create_validator.ex
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do
+ use Ecto.Schema
+
+ alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+
+ import Ecto.Changeset
+
+ @primary_key false
+
+ embedded_schema do
+ field(:id, Types.ObjectID, primary_key: true)
+ field(:actor, Types.ObjectID)
+ field(:type, :string)
+ field(:to, {:array, :string})
+ field(:cc, {:array, :string})
+ field(:bto, {:array, :string}, default: [])
+ field(:bcc, {:array, :string}, default: [])
+
+ embeds_one(:object, NoteValidator)
+ end
+
+ def cast_data(data) do
+ cast(%__MODULE__{}, data, __schema__(:fields))
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
new file mode 100644
index 000000000..49546ceaa
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
@@ -0,0 +1,57 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
+ use Ecto.Schema
+
+ alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+ alias Pleroma.Web.ActivityPub.Utils
+
+ import Ecto.Changeset
+ import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+ @primary_key false
+
+ embedded_schema do
+ field(:id, Types.ObjectID, primary_key: true)
+ field(:type, :string)
+ field(:object, Types.ObjectID)
+ field(:actor, Types.ObjectID)
+ field(:context, :string)
+ field(:to, {:array, :string})
+ field(:cc, {:array, :string})
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> cast(data, [:id, :type, :object, :actor, :context, :to, :cc])
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["Like"])
+ |> validate_required([:id, :type, :object, :actor, :context, :to, :cc])
+ |> validate_actor_presence()
+ |> validate_object_presence()
+ |> validate_existing_like()
+ end
+
+ def validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do
+ if Utils.get_existing_like(actor, %{data: %{"id" => object}}) do
+ cng
+ |> add_error(:actor, "already liked this object")
+ |> add_error(:object, "already liked by this actor")
+ else
+ cng
+ end
+ end
+
+ def validate_existing_like(cng), do: cng
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
new file mode 100644
index 000000000..c95b622e4
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
+ use Ecto.Schema
+
+ alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+
+ import Ecto.Changeset
+
+ @primary_key false
+
+ embedded_schema do
+ field(:id, Types.ObjectID, primary_key: true)
+ field(:to, {:array, :string}, default: [])
+ field(:cc, {:array, :string}, default: [])
+ field(:bto, {:array, :string}, default: [])
+ field(:bcc, {:array, :string}, default: [])
+ # TODO: Write type
+ field(:tag, {:array, :map}, default: [])
+ field(:type, :string)
+ field(:content, :string)
+ field(:context, :string)
+ field(:actor, Types.ObjectID)
+ field(:attributedTo, Types.ObjectID)
+ field(:summary, :string)
+ field(:published, Types.DateTime)
+ # TODO: Write type
+ field(:emoji, :map, default: %{})
+ field(:sensitive, :boolean, default: false)
+ # TODO: Write type
+ field(:attachment, {:array, :map}, default: [])
+ field(:replies_count, :integer, default: 0)
+ field(:like_count, :integer, default: 0)
+ field(:announcement_count, :integer, default: 0)
+ field(:inRepyTo, :string)
+
+ field(:likes, {:array, :string}, default: [])
+ field(:announcements, {:array, :string}, default: [])
+
+ # see if needed
+ field(:conversation, :string)
+ field(:context_id, :string)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> cast(data, __schema__(:fields))
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["Note"])
+ |> validate_required([:id, :actor, :to, :cc, :type, :content, :context])
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex b/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex
new file mode 100644
index 000000000..4f412fcde
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex
@@ -0,0 +1,34 @@
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime do
+ @moduledoc """
+ The AP standard defines the date fields in AP as xsd:DateTime. Elixir's
+ DateTime can't parse this, but it can parse the related iso8601. This
+ module punches the date until it looks like iso8601 and normalizes to
+ it.
+
+ DateTimes without a timezone offset are treated as UTC.
+
+ Reference: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-published
+ """
+ use Ecto.Type
+
+ def type, do: :string
+
+ def cast(datetime) when is_binary(datetime) do
+ with {:ok, datetime, _} <- DateTime.from_iso8601(datetime) do
+ {:ok, DateTime.to_iso8601(datetime)}
+ else
+ {:error, :missing_offset} -> cast("#{datetime}Z")
+ _e -> :error
+ end
+ end
+
+ def cast(_), do: :error
+
+ def dump(data) do
+ {:ok, data}
+ end
+
+ def load(data) do
+ {:ok, data}
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex b/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex
new file mode 100644
index 000000000..ee10be0b0
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex
@@ -0,0 +1,33 @@
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID do
+ use Ecto.Type
+
+ def type, do: :string
+
+ def cast(object) when is_binary(object) do
+ # Host has to be present and scheme has to be an http scheme (for now)
+ case URI.parse(object) do
+ %URI{host: nil} ->
+ :error
+
+ %URI{scheme: scheme} when scheme in ["https", "http"] ->
+ {:ok, object}
+
+ _ ->
+ :error
+ end
+ end
+
+ def cast(%{"id" => object}), do: cast(object)
+
+ def cast(_) do
+ :error
+ end
+
+ def dump(data) do
+ {:ok, data}
+ end
+
+ def load(data) do
+ {:ok, data}
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
new file mode 100644
index 000000000..7ccee54c9
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Pipeline do
+ alias Pleroma.Activity
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.MRF
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.ActivityPub.SideEffects
+ alias Pleroma.Web.Federator
+
+ @spec common_pipeline(map(), keyword()) :: {:ok, Activity.t(), keyword()} | {:error, any()}
+ def common_pipeline(object, meta) do
+ with {_, {:ok, validated_object, meta}} <-
+ {:validate_object, ObjectValidator.validate(object, meta)},
+ {_, {:ok, mrfd_object}} <- {:mrf_object, MRF.filter(validated_object)},
+ {_, {:ok, %Activity{} = activity, meta}} <-
+ {:persist_object, ActivityPub.persist(mrfd_object, meta)},
+ {_, {:ok, %Activity{} = activity, meta}} <-
+ {:execute_side_effects, SideEffects.handle(activity, meta)},
+ {_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
+ {:ok, activity, meta}
+ else
+ {:mrf_object, {:reject, _}} -> {:ok, nil, meta}
+ e -> {:error, e}
+ end
+ end
+
+ defp maybe_federate(activity, meta) do
+ with {:ok, local} <- Keyword.fetch(meta, :local) do
+ if local do
+ Federator.publish(activity)
+ {:ok, :federated}
+ else
+ {:ok, :not_federated}
+ end
+ else
+ _e -> {:error, :badarg}
+ end
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
new file mode 100644
index 000000000..666a4e310
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -0,0 +1,28 @@
+defmodule Pleroma.Web.ActivityPub.SideEffects do
+ @moduledoc """
+ This module looks at an inserted object and executes the side effects that it
+ implies. For example, a `Like` activity will increase the like count on the
+ liked object, a `Follow` activity will add the user to the follower
+ collection, and so on.
+ """
+ alias Pleroma.Notification
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Utils
+
+ def handle(object, meta \\ [])
+
+ # Tasks this handles:
+ # - Add like to object
+ # - Set up notification
+ def handle(%{data: %{"type" => "Like"}} = object, meta) do
+ liked_object = Object.get_by_ap_id(object.data["object"])
+ Utils.add_like_to_object(object, liked_object)
+ Notification.create_notifications(object)
+ {:ok, object, meta}
+ end
+
+ # Nothing to do
+ def handle(object, meta) do
+ {:ok, object, meta}
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 9cd3de705..0a8ad62ad 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -13,6 +13,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+ alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Federator
@@ -229,7 +232,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "url", url["href"])
end
- def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do
+ def fix_url(%{"type" => object_type, "url" => url} = object)
+ when object_type in ["Video", "Audio"] and is_list(url) do
first_element = Enum.at(url, 0)
link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
@@ -398,7 +402,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
options
)
- when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer"] do
+ when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer", "Audio"] do
actor = Containment.get_actor(data)
data =
@@ -608,17 +612,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> handle_incoming(options)
end
- def handle_incoming(
- %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data,
- _options
- ) do
- with actor <- Containment.get_actor(data),
- {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
- {:ok, object} <- get_obj_helper(object_id),
- {:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do
+ def handle_incoming(%{"type" => "Like"} = data, _options) do
+ with {_, {:ok, cast_data_sym}} <-
+ {:casting_data,
+ data |> LikeValidator.cast_data() |> Ecto.Changeset.apply_action(:insert)},
+ cast_data = ObjectValidator.stringify_keys(Map.from_struct(cast_data_sym)),
+ :ok <- ObjectValidator.fetch_actor_and_object(cast_data),
+ {_, {:ok, cast_data}} <- {:ensure_context_presence, ensure_context_presence(cast_data)},
+ {_, {:ok, cast_data}} <-
+ {:ensure_recipients_presence, ensure_recipients_presence(cast_data)},
+ {_, {:ok, activity, _meta}} <-
+ {:common_pipeline, Pipeline.common_pipeline(cast_data, local: false)} do
{:ok, activity}
else
- _e -> :error
+ e -> {:error, e}
end
end
@@ -1108,13 +1115,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def add_mention_tags(object) do
- mentions =
- object
- |> Utils.get_notified_from_object()
- |> Enum.map(&build_mention_tag/1)
+ {enabled_receivers, disabled_receivers} = Utils.get_notified_from_object(object)
+ potential_receivers = enabled_receivers ++ disabled_receivers
+ mentions = Enum.map(potential_receivers, &build_mention_tag/1)
tags = object["tag"] || []
-
Map.put(object, "tag", tags ++ mentions)
end
@@ -1244,4 +1249,45 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def maybe_fix_user_url(data), do: data
def maybe_fix_user_object(data), do: maybe_fix_user_url(data)
+
+ defp ensure_context_presence(%{"context" => context} = data) when is_binary(context),
+ do: {:ok, data}
+
+ defp ensure_context_presence(%{"object" => object} = data) when is_binary(object) do
+ with %{data: %{"context" => context}} when is_binary(context) <- Object.normalize(object) do
+ {:ok, Map.put(data, "context", context)}
+ else
+ _ ->
+ {:error, :no_context}
+ end
+ end
+
+ defp ensure_context_presence(_) do
+ {:error, :no_context}
+ end
+
+ defp ensure_recipients_presence(%{"to" => [_ | _], "cc" => [_ | _]} = data),
+ do: {:ok, data}
+
+ defp ensure_recipients_presence(%{"object" => object} = data) do
+ case Object.normalize(object) do
+ %{data: %{"actor" => actor}} ->
+ data =
+ data
+ |> Map.put("to", [actor])
+ |> Map.put("cc", data["cc"] || [])
+
+ {:ok, data}
+
+ nil ->
+ {:error, :no_object}
+
+ _ ->
+ {:error, :no_actor}
+ end
+ end
+
+ defp ensure_recipients_presence(_) do
+ {:error, :no_object}
+ end
end
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index c65bbed67..2d685ecc0 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -795,102 +795,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
ActivityPub.fetch_activities([], params, :offset)
end
- def parse_report_group(activity) do
- reports = get_reports_by_status_id(activity["id"])
- max_date = Enum.max_by(reports, &NaiveDateTime.from_iso8601!(&1.data["published"]))
- actors = Enum.map(reports, & &1.user_actor)
- [%{data: %{"object" => [account_id | _]}} | _] = reports
-
- account =
- AccountView.render("show.json", %{
- user: User.get_by_ap_id(account_id)
- })
-
- status = get_status_data(activity)
-
- %{
- date: max_date.data["published"],
- account: account,
- status: status,
- actors: Enum.uniq(actors),
- reports: reports
- }
- end
-
- defp get_status_data(status) do
- case status["deleted"] do
- true ->
- %{
- "id" => status["id"],
- "deleted" => true
- }
-
- _ ->
- Activity.get_by_ap_id(status["id"])
- end
- end
-
- def get_reports_by_status_id(ap_id) do
- from(a in Activity,
- where: fragment("(?)->>'type' = 'Flag'", a.data),
- where: fragment("(?)->'object' @> ?", a.data, ^[%{id: ap_id}]),
- or_where: fragment("(?)->'object' @> ?", a.data, ^[ap_id])
- )
- |> Activity.with_preloaded_user_actor()
- |> Repo.all()
- end
-
- @spec get_reports_grouped_by_status([String.t()]) :: %{
- required(:groups) => [
- %{
- required(:date) => String.t(),
- required(:account) => %{},
- required(:status) => %{},
- required(:actors) => [%User{}],
- required(:reports) => [%Activity{}]
- }
- ]
- }
- def get_reports_grouped_by_status(activity_ids) do
- parsed_groups =
- activity_ids
- |> Enum.map(fn id ->
- id
- |> build_flag_object()
- |> parse_report_group()
- end)
-
- %{
- groups: parsed_groups
- }
- end
-
- @spec get_reported_activities() :: [
- %{
- required(:activity) => String.t(),
- required(:date) => String.t()
- }
- ]
- def get_reported_activities do
- reported_activities_query =
- from(a in Activity,
- where: fragment("(?)->>'type' = 'Flag'", a.data),
- select: %{
- activity: fragment("jsonb_array_elements((? #- '{object,0}')->'object')", a.data)
- },
- group_by: fragment("activity")
- )
-
- from(a in subquery(reported_activities_query),
- distinct: true,
- select: %{
- id: fragment("COALESCE(?->>'id'::text, ? #>> '{}')", a.activity, a.activity)
- }
- )
- |> Repo.all()
- |> Enum.map(& &1.id)
- end
-
def update_report_state(%Activity{} = activity, state)
when state in @strip_status_report_states do
{:ok, stripped_activity} = strip_report_status_data(activity)
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index 0368df1e9..ca5439920 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -715,14 +715,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> render("index.json", %{reports: reports})
end
- def list_grouped_reports(conn, _params) do
- statuses = Utils.get_reported_activities()
-
- conn
- |> put_view(ReportView)
- |> render("index_grouped.json", Utils.get_reports_grouped_by_status(statuses))
- end
-
def report_show(conn, %{"id" => id}) do
with %Activity{} = report <- Activity.get_by_id(id) do
conn
diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex
index fc8733ce8..ca0bcebc7 100644
--- a/lib/pleroma/web/admin_api/views/report_view.ex
+++ b/lib/pleroma/web/admin_api/views/report_view.ex
@@ -4,7 +4,7 @@
defmodule Pleroma.Web.AdminAPI.ReportView do
use Pleroma.Web, :view
- alias Pleroma.Activity
+
alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Web.AdminAPI.Report
@@ -44,32 +44,6 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
}
end
- def render("index_grouped.json", %{groups: groups}) do
- reports =
- Enum.map(groups, fn group ->
- status =
- case group.status do
- %Activity{} = activity -> StatusView.render("show.json", %{activity: activity})
- _ -> group.status
- end
-
- %{
- date: group[:date],
- account: group[:account],
- status: Map.put_new(status, "deleted", false),
- actors: Enum.map(group[:actors], &merge_account_views/1),
- reports:
- group[:reports]
- |> Enum.map(&Report.extract_report_info(&1))
- |> Enum.map(&render(__MODULE__, "show.json", &1))
- }
- end)
-
- %{
- reports: reports
- }
- end
-
def render("index_notes.json", %{notes: notes}) when is_list(notes) do
Enum.map(notes, &render(__MODULE__, "show_note.json", &1))
end
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 091011c6b..636cf3301 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -12,6 +12,8 @@ defmodule Pleroma.Web.CommonAPI do
alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
@@ -19,6 +21,7 @@ defmodule Pleroma.Web.CommonAPI do
import Pleroma.Web.CommonAPI.Utils
require Pleroma.Constants
+ require Logger
def follow(follower, followed) do
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
@@ -109,18 +112,51 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- def favorite(id_or_ap_id, user) do
- with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)},
- object <- Object.normalize(activity),
- like_activity <- Utils.get_existing_like(user.ap_id, object) do
- if like_activity do
- {:ok, like_activity, object}
- else
- ActivityPub.like(user, object)
- end
+ @spec favorite(User.t(), binary()) :: {:ok, Activity.t() | :already_liked} | {:error, any()}
+ def favorite(%User{} = user, id) do
+ case favorite_helper(user, id) do
+ {:ok, _} = res ->
+ res
+
+ {:error, :not_found} = res ->
+ res
+
+ {:error, e} ->
+ Logger.error("Could not favorite #{id}. Error: #{inspect(e, pretty: true)}")
+ {:error, dgettext("errors", "Could not favorite")}
+ end
+ end
+
+ def favorite_helper(user, id) do
+ with {_, %Activity{object: object}} <- {:find_object, Activity.get_by_id_with_object(id)},
+ {_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)},
+ {_, {:ok, %Activity{} = activity, _meta}} <-
+ {:common_pipeline,
+ Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do
+ {:ok, activity}
else
- {:find_activity, _} -> {:error, :not_found}
- _ -> {:error, dgettext("errors", "Could not favorite")}
+ {:find_object, _} ->
+ {:error, :not_found}
+
+ {:common_pipeline,
+ {
+ :error,
+ {
+ :validate_object,
+ {
+ :error,
+ changeset
+ }
+ }
+ }} = e ->
+ if {:object, {"already liked by this actor", []}} in changeset.errors do
+ {:ok, :already_liked}
+ else
+ {:error, e}
+ end
+
+ e ->
+ {:error, e}
end
end
@@ -358,7 +394,7 @@ defmodule Pleroma.Web.CommonAPI do
def thread_muted?(%{id: nil} = _user, _activity), do: false
def thread_muted?(user, activity) do
- ThreadMute.check_muted(user.id, activity.data["context"]) != []
+ ThreadMute.exists?(user.id, activity.data["context"])
end
def report(user, %{"account_id" => account_id} = data) do
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index ad293cda9..b49523ec3 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -34,7 +34,12 @@ defmodule Pleroma.Web.ControllerHelper do
defp param_to_integer(_, default), do: default
- def add_link_headers(conn, activities, extra_params \\ %{}) do
+ def add_link_headers(conn, activities, extra_params \\ %{})
+
+ def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _activities, _extra_params),
+ do: conn
+
+ def add_link_headers(conn, activities, extra_params) do
case List.last(activities) do
%{id: max_id} ->
params =
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index fb80dd348..21bc3d5a5 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -62,11 +62,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
when action not in [:create, :show, :statuses]
)
- @relations [:follow, :unfollow]
+ @relationship_actions [:follow, :unfollow]
@needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a
- plug(RateLimiter, [name: :relations_id_action, params: ["id", "uri"]] when action in @relations)
- plug(RateLimiter, [name: :relations_actions] when action in @relations)
+ plug(
+ RateLimiter,
+ [name: :relation_id_action, params: ["id", "uri"]] when action in @relationship_actions
+ )
+
+ plug(RateLimiter, [name: :relations_actions] when action in @relationship_actions)
plug(RateLimiter, [name: :app_account_creation] when action == :create)
plug(:assign_account_by_id when action in @needs_account)
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index 37afe6949..ec8f0d8a0 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -207,9 +207,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
end
@doc "POST /api/v1/statuses/:id/favourite"
- def favourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
- with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user),
- %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
+ def favourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do
+ with {:ok, _fav} <- CommonAPI.favorite(user, activity_id),
+ %Activity{} = activity <- Activity.get_by_id(activity_id) do
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
end
end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 2bf711386..99e62f580 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -5,12 +5,30 @@
defmodule Pleroma.Web.MastodonAPI.AccountView do
use Pleroma.Web, :view
+ alias Pleroma.FollowingRelationship
alias Pleroma.User
+ alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MediaProxy
def render("index.json", %{users: users} = opts) do
+ reading_user = opts[:for]
+
+ relationships_opt =
+ cond do
+ Map.has_key?(opts, :relationships) ->
+ opts[:relationships]
+
+ is_nil(reading_user) ->
+ UserRelationship.view_relationships_option(nil, [])
+
+ true ->
+ UserRelationship.view_relationships_option(reading_user, users)
+ end
+
+ opts = Map.put(opts, :relationships, relationships_opt)
+
users
|> render_many(AccountView, "show.json", opts)
|> Enum.filter(&Enum.any?/1)
@@ -27,7 +45,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
id: to_string(user.id),
acct: user.nickname,
username: username_from_nickname(user.nickname),
- url: User.profile_url(user)
+ url: user.uri || user.ap_id
}
end
@@ -35,27 +53,107 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
%{}
end
- def render("relationship.json", %{user: %User{} = user, target: %User{} = target}) do
- follow_state = User.get_follow_state(user, target)
+ def render(
+ "relationship.json",
+ %{user: %User{} = reading_user, target: %User{} = target} = opts
+ ) do
+ user_relationships = get_in(opts, [:relationships, :user_relationships])
+ following_relationships = get_in(opts, [:relationships, :following_relationships])
+
+ follow_state =
+ if following_relationships do
+ user_to_target_following_relation =
+ FollowingRelationship.find(following_relationships, reading_user, target)
+ User.get_follow_state(reading_user, target, user_to_target_following_relation)
+ else
+ User.get_follow_state(reading_user, target)
+ end
+
+ followed_by =
+ if following_relationships do
+ case FollowingRelationship.find(following_relationships, target, reading_user) do
+ %{state: "accept"} -> true
+ _ -> false
+ end
+ else
+ User.following?(target, reading_user)
+ end
+
+ # NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
%{
id: to_string(target.id),
following: follow_state == "accept",
- followed_by: User.following?(target, user),
- blocking: User.blocks_user?(user, target),
- blocked_by: User.blocks_user?(target, user),
- muting: User.mutes?(user, target),
- muting_notifications: User.muted_notifications?(user, target),
- subscribing: User.subscribed_to?(user, target),
+ followed_by: followed_by,
+ blocking:
+ UserRelationship.exists?(
+ user_relationships,
+ :block,
+ reading_user,
+ target,
+ &User.blocks_user?(&1, &2)
+ ),
+ blocked_by:
+ UserRelationship.exists?(
+ user_relationships,
+ :block,
+ target,
+ reading_user,
+ &User.blocks_user?(&1, &2)
+ ),
+ muting:
+ UserRelationship.exists?(
+ user_relationships,
+ :mute,
+ reading_user,
+ target,
+ &User.mutes?(&1, &2)
+ ),
+ muting_notifications:
+ UserRelationship.exists?(
+ user_relationships,
+ :notification_mute,
+ reading_user,
+ target,
+ &User.muted_notifications?(&1, &2)
+ ),
+ subscribing:
+ UserRelationship.exists?(
+ user_relationships,
+ :inverse_subscription,
+ target,
+ reading_user,
+ &User.subscribed_to?(&2, &1)
+ ),
requested: follow_state == "pending",
- domain_blocking: User.blocks_domain?(user, target),
- showing_reblogs: User.showing_reblogs?(user, target),
+ domain_blocking: User.blocks_domain?(reading_user, target),
+ showing_reblogs:
+ not UserRelationship.exists?(
+ user_relationships,
+ :reblog_mute,
+ reading_user,
+ target,
+ &User.muting_reblogs?(&1, &2)
+ ),
endorsed: false
}
end
- def render("relationships.json", %{user: user, targets: targets}) do
- render_many(targets, AccountView, "relationship.json", user: user, as: :target)
+ def render("relationships.json", %{user: user, targets: targets} = opts) do
+ relationships_opt =
+ cond do
+ Map.has_key?(opts, :relationships) ->
+ opts[:relationships]
+
+ is_nil(user) ->
+ UserRelationship.view_relationships_option(nil, [])
+
+ true ->
+ UserRelationship.view_relationships_option(user, targets)
+ end
+
+ render_opts = %{as: :target, user: user, relationships: relationships_opt}
+ render_many(targets, AccountView, "relationship.json", render_opts)
end
defp do_render("show.json", %{user: user} = opts) do
@@ -93,7 +191,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
}
end)
- relationship = render("relationship.json", %{user: opts[:for], target: user})
+ relationship =
+ render("relationship.json", %{
+ user: opts[:for],
+ target: user,
+ relationships: opts[:relationships]
+ })
%{
id: to_string(user.id),
@@ -106,7 +209,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
following_count: following_count,
statuses_count: user.note_count,
note: user.bio || "",
- url: User.profile_url(user),
+ url: user.uri || user.ap_id,
avatar: image,
avatar_static: image,
header: header,
diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex
index 33145c484..ae87d4701 100644
--- a/lib/pleroma/web/mastodon_api/views/notification_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex
@@ -8,24 +8,86 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.User
+ alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.StatusView
- def render("index.json", %{notifications: notifications, for: user}) do
- safe_render_many(notifications, NotificationView, "show.json", %{for: user})
+ def render("index.json", %{notifications: notifications, for: reading_user} = opts) do
+ activities = Enum.map(notifications, & &1.activity)
+
+ parent_activities =
+ activities
+ |> Enum.filter(
+ &(Activity.mastodon_notification_type(&1) in [
+ "favourite",
+ "reblog",
+ "pleroma:emoji_reaction"
+ ])
+ )
+ |> Enum.map(& &1.data["object"])
+ |> Activity.create_by_object_ap_id()
+ |> Activity.with_preloaded_object(:left)
+ |> Pleroma.Repo.all()
+
+ relationships_opt =
+ cond do
+ Map.has_key?(opts, :relationships) ->
+ opts[:relationships]
+
+ is_nil(reading_user) ->
+ UserRelationship.view_relationships_option(nil, [])
+
+ true ->
+ move_activities_targets =
+ activities
+ |> Enum.filter(&(Activity.mastodon_notification_type(&1) == "move"))
+ |> Enum.map(&User.get_cached_by_ap_id(&1.data["target"]))
+
+ actors =
+ activities
+ |> Enum.map(fn a -> User.get_cached_by_ap_id(a.data["actor"]) end)
+ |> Enum.filter(& &1)
+ |> Kernel.++(move_activities_targets)
+
+ UserRelationship.view_relationships_option(reading_user, actors)
+ end
+
+ opts = %{
+ for: reading_user,
+ parent_activities: parent_activities,
+ relationships: relationships_opt
+ }
+
+ safe_render_many(notifications, NotificationView, "show.json", opts)
end
- def render("show.json", %{
- notification: %Notification{activity: activity} = notification,
- for: user
- }) do
+ def render(
+ "show.json",
+ %{
+ notification: %Notification{activity: activity} = notification,
+ for: reading_user
+ } = opts
+ ) do
actor = User.get_cached_by_ap_id(activity.data["actor"])
- parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
+
+ parent_activity_fn = fn ->
+ if opts[:parent_activities] do
+ Activity.Queries.find_by_object_ap_id(opts[:parent_activities], activity.data["object"])
+ else
+ Activity.get_create_by_object_ap_id(activity.data["object"])
+ end
+ end
+
mastodon_type = Activity.mastodon_notification_type(activity)
- with %{id: _} = account <- AccountView.render("show.json", %{user: actor, for: user}) do
+ with %{id: _} = account <-
+ AccountView.render("show.json", %{
+ user: actor,
+ for: reading_user,
+ relationships: opts[:relationships]
+ }) do
response = %{
id: to_string(notification.id),
type: mastodon_type,
@@ -36,24 +98,28 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
}
}
+ render_opts = %{relationships: opts[:relationships]}
+
case mastodon_type do
"mention" ->
- put_status(response, activity, user)
+ put_status(response, activity, reading_user, render_opts)
"favourite" ->
- put_status(response, parent_activity, user)
+ put_status(response, parent_activity_fn.(), reading_user, render_opts)
"reblog" ->
- put_status(response, parent_activity, user)
+ put_status(response, parent_activity_fn.(), reading_user, render_opts)
"move" ->
- put_target(response, activity, user)
+ put_target(response, activity, reading_user, render_opts)
"follow" ->
response
"pleroma:emoji_reaction" ->
- put_status(response, parent_activity, user) |> put_emoji(activity)
+ response
+ |> put_status(parent_activity_fn.(), reading_user, render_opts)
+ |> put_emoji(activity)
_ ->
nil
@@ -64,16 +130,21 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
end
defp put_emoji(response, activity) do
- response
- |> Map.put(:emoji, activity.data["content"])
+ Map.put(response, :emoji, activity.data["content"])
end
- defp put_status(response, activity, user) do
- Map.put(response, :status, StatusView.render("show.json", %{activity: activity, for: user}))
+ defp put_status(response, activity, reading_user, opts) do
+ status_render_opts = Map.merge(opts, %{activity: activity, for: reading_user})
+ status_render = StatusView.render("show.json", status_render_opts)
+
+ Map.put(response, :status, status_render)
end
- defp put_target(response, activity, user) do
- target = User.get_cached_by_ap_id(activity.data["target"])
- Map.put(response, :target, AccountView.render("show.json", %{user: target, for: user}))
+ defp put_target(response, activity, reading_user, opts) do
+ target_user = User.get_cached_by_ap_id(activity.data["target"])
+ target_render_opts = Map.merge(opts, %{user: target_user, for: reading_user})
+ target_render = AccountView.render("show.json", target_render_opts)
+
+ Map.put(response, :target, target_render)
end
end
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index f7469cdff..cea76e735 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView
@@ -71,10 +72,43 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
def render("index.json", opts) do
- replied_to_activities = get_replied_to_activities(opts.activities)
- opts = Map.put(opts, :replied_to_activities, replied_to_activities)
+ reading_user = opts[:for]
- safe_render_many(opts.activities, StatusView, "show.json", opts)
+ # To do: check AdminAPIControllerTest on the reasons behind nil activities in the list
+ activities = Enum.filter(opts.activities, & &1)
+ replied_to_activities = get_replied_to_activities(activities)
+
+ parent_activities =
+ activities
+ |> Enum.filter(&(&1.data["type"] == "Announce" && &1.data["object"]))
+ |> Enum.map(&Object.normalize(&1).data["id"])
+ |> Activity.create_by_object_ap_id()
+ |> Activity.with_preloaded_object(:left)
+ |> Activity.with_preloaded_bookmark(reading_user)
+ |> Activity.with_set_thread_muted_field(reading_user)
+ |> Repo.all()
+
+ relationships_opt =
+ cond do
+ Map.has_key?(opts, :relationships) ->
+ opts[:relationships]
+
+ is_nil(reading_user) ->
+ UserRelationship.view_relationships_option(nil, [])
+
+ true ->
+ actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
+
+ UserRelationship.view_relationships_option(reading_user, actors)
+ end
+
+ opts =
+ opts
+ |> Map.put(:replied_to_activities, replied_to_activities)
+ |> Map.put(:parent_activities, parent_activities)
+ |> Map.put(:relationships, relationships_opt)
+
+ safe_render_many(activities, StatusView, "show.json", opts)
end
def render(
@@ -85,17 +119,25 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
created_at = Utils.to_masto_date(activity.data["published"])
activity_object = Object.normalize(activity)
- reblogged_activity =
- Activity.create_by_object_ap_id(activity_object.data["id"])
- |> Activity.with_preloaded_bookmark(opts[:for])
- |> Activity.with_set_thread_muted_field(opts[:for])
- |> Repo.one()
+ reblogged_parent_activity =
+ if opts[:parent_activities] do
+ Activity.Queries.find_by_object_ap_id(
+ opts[:parent_activities],
+ activity_object.data["id"]
+ )
+ else
+ Activity.create_by_object_ap_id(activity_object.data["id"])
+ |> Activity.with_preloaded_bookmark(opts[:for])
+ |> Activity.with_set_thread_muted_field(opts[:for])
+ |> Repo.one()
+ end
- reblogged = render("show.json", Map.put(opts, :activity, reblogged_activity))
+ reblog_rendering_opts = Map.put(opts, :activity, reblogged_parent_activity)
+ reblogged = render("show.json", reblog_rendering_opts)
favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || [])
- bookmarked = Activity.get_bookmark(reblogged_activity, opts[:for]) != nil
+ bookmarked = Activity.get_bookmark(reblogged_parent_activity, opts[:for]) != nil
mentions =
activity.recipients
@@ -107,7 +149,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
id: to_string(activity.id),
uri: activity_object.data["id"],
url: activity_object.data["id"],
- account: AccountView.render("show.json", %{user: user, for: opts[:for]}),
+ account:
+ AccountView.render("show.json", %{
+ user: user,
+ for: opts[:for],
+ relationships: opts[:relationships]
+ }),
in_reply_to_id: nil,
in_reply_to_account_id: nil,
reblog: reblogged,
@@ -116,7 +163,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reblogs_count: 0,
replies_count: 0,
favourites_count: 0,
- reblogged: reblogged?(reblogged_activity, opts[:for]),
+ reblogged: reblogged?(reblogged_parent_activity, opts[:for]),
favourited: present?(favorited),
bookmarked: present?(bookmarked),
muted: false,
@@ -183,9 +230,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
thread_muted? =
- case activity.thread_muted? do
- thread_muted? when is_boolean(thread_muted?) -> thread_muted?
- nil -> (opts[:for] && CommonAPI.thread_muted?(opts[:for], activity)) || false
+ cond do
+ is_nil(opts[:for]) -> false
+ is_boolean(activity.thread_muted?) -> activity.thread_muted?
+ true -> CommonAPI.thread_muted?(opts[:for], activity)
end
attachment_data = object.data["attachment"] || []
@@ -253,11 +301,26 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
_ -> []
end
+ muted =
+ thread_muted? ||
+ UserRelationship.exists?(
+ get_in(opts, [:relationships, :user_relationships]),
+ :mute,
+ opts[:for],
+ user,
+ fn for_user, user -> User.mutes?(for_user, user) end
+ )
+
%{
id: to_string(activity.id),
uri: object.data["id"],
url: url,
- account: AccountView.render("show.json", %{user: user, for: opts[:for]}),
+ account:
+ AccountView.render("show.json", %{
+ user: user,
+ for: opts[:for],
+ relationships: opts[:relationships]
+ }),
in_reply_to_id: reply_to && to_string(reply_to.id),
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
reblog: nil,
@@ -270,7 +333,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reblogged: reblogged?(activity, opts[:for]),
favourited: present?(favorited),
bookmarked: present?(bookmarked),
- muted: thread_muted? || User.mutes?(opts[:for], user),
+ muted: muted,
pinned: pinned?(activity, user),
sensitive: sensitive,
spoiler_text: summary,
@@ -421,7 +484,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
def render_content(%{data: %{"type" => object_type}} = object)
- when object_type in ["Video", "Event"] do
+ when object_type in ["Video", "Event", "Audio"] do
with name when not is_nil(name) and name != "" <- object.data["name"] do
"<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}"
else
diff --git a/lib/pleroma/web/metadata.ex b/lib/pleroma/web/metadata.ex
index c9aac27dc..a9f70c43e 100644
--- a/lib/pleroma/web/metadata.ex
+++ b/lib/pleroma/web/metadata.ex
@@ -6,7 +6,12 @@ defmodule Pleroma.Web.Metadata do
alias Phoenix.HTML
def build_tags(params) do
- Enum.reduce(Pleroma.Config.get([__MODULE__, :providers], []), "", fn parser, acc ->
+ providers = [
+ Pleroma.Web.Metadata.Providers.RestrictIndexing
+ | Pleroma.Config.get([__MODULE__, :providers], [])
+ ]
+
+ Enum.reduce(providers, "", fn parser, acc ->
rendered_html =
params
|> parser.build_tags()
diff --git a/lib/pleroma/web/metadata/opengraph.ex b/lib/pleroma/web/metadata/opengraph.ex
index 21446ac77..68c871e71 100644
--- a/lib/pleroma/web/metadata/opengraph.ex
+++ b/lib/pleroma/web/metadata/opengraph.ex
@@ -68,7 +68,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
property: "og:title",
content: Utils.user_name_string(user)
], []},
- {:meta, [property: "og:url", content: User.profile_url(user)], []},
+ {:meta, [property: "og:url", content: user.uri || user.ap_id], []},
{:meta, [property: "og:description", content: truncated_bio], []},
{:meta, [property: "og:type", content: "website"], []},
{:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))], []},
diff --git a/lib/pleroma/web/metadata/restrict_indexing.ex b/lib/pleroma/web/metadata/restrict_indexing.ex
new file mode 100644
index 000000000..f15607896
--- /dev/null
+++ b/lib/pleroma/web/metadata/restrict_indexing.ex
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do
+ @behaviour Pleroma.Web.Metadata.Providers.Provider
+
+ @moduledoc """
+ Restricts indexing of remote users.
+ """
+
+ @impl true
+ def build_tags(%{user: %{local: false}}) do
+ [
+ {:meta,
+ [
+ name: "robots",
+ content: "noindex, noarchive"
+ ], []}
+ ]
+ end
+
+ @impl true
+ def build_tags(%{user: %{local: true}}), do: []
+end
diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex
index e97c398dc..8e2b51508 100644
--- a/lib/pleroma/web/rel_me.ex
+++ b/lib/pleroma/web/rel_me.ex
@@ -3,11 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RelMe do
- @hackney_options [
+ @options [
pool: :media,
- recv_timeout: 2_000,
- max_body: 2_000_000,
- with_body: true
+ max_body: 2_000_000
]
if Pleroma.Config.get(:env) == :test do
@@ -25,8 +23,18 @@ defmodule Pleroma.Web.RelMe do
def parse(_), do: {:error, "No URL provided"}
defp parse_url(url) do
+ opts =
+ if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
+ Keyword.merge(@options,
+ recv_timeout: 2_000,
+ with_body: true
+ )
+ else
+ @options
+ end
+
with {:ok, %Tesla.Env{body: html, status: status}} when status in 200..299 <-
- Pleroma.HTTP.get(url, [], adapter: @hackney_options),
+ Pleroma.HTTP.get(url, [], adapter: opts),
{:ok, html_tree} <- Floki.parse_document(html),
data <-
Floki.attribute(html_tree, "link[rel~=me]", "href") ++
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index 0779065ee..40980def8 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -3,11 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parser do
- @hackney_options [
+ @options [
pool: :media,
- recv_timeout: 2_000,
- max_body: 2_000_000,
- with_body: true
+ max_body: 2_000_000
]
defp parsers do
@@ -77,8 +75,18 @@ defmodule Pleroma.Web.RichMedia.Parser do
end
defp parse_url(url) do
+ opts =
+ if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
+ Keyword.merge(@options,
+ recv_timeout: 2_000,
+ with_body: true
+ )
+ else
+ @options
+ end
+
try do
- {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
+ {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: opts)
html
|> parse_html()
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index a22f744c1..5a0902739 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -186,7 +186,6 @@ defmodule Pleroma.Web.Router do
patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email)
get("/reports", AdminAPIController, :list_reports)
- get("/grouped_reports", AdminAPIController, :list_grouped_reports)
get("/reports/:id", AdminAPIController, :report_show)
patch("/reports", AdminAPIController, :reports_update)
post("/reports/:id/notes", AdminAPIController, :report_notes_create)
diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex
index 29f992a67..abfed21c8 100644
--- a/lib/pleroma/web/streamer/worker.ex
+++ b/lib/pleroma/web/streamer/worker.ex
@@ -130,7 +130,7 @@ defmodule Pleroma.Web.Streamer.Worker do
defp should_send?(%User{} = user, %Activity{} = item) do
%{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} =
- User.outgoing_relations_ap_ids(user, [:block, :mute, :reblog_mute])
+ User.outgoing_relationships_ap_ids(user, [:block, :mute, :reblog_mute])
recipient_blocks = MapSet.new(blocked_ap_ids ++ muted_ap_ids)
recipients = MapSet.new(item.recipients)
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/_user_card.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/_user_card.html.eex
index c7789f9ac..2a7582d45 100644
--- a/lib/pleroma/web/templates/static_fe/static_fe/_user_card.html.eex
+++ b/lib/pleroma/web/templates/static_fe/static_fe/_user_card.html.eex
@@ -1,5 +1,5 @@
<div class="p-author h-card">
- <a class="u-url" rel="author noopener" href="<%= User.profile_url(@user) %>">
+ <a class="u-url" rel="author noopener" href="<%= (@user.uri || @user.ap_id) %>">
<div class="avatar">
<img src="<%= User.avatar_url(@user) |> MediaProxy.url %>" width="48" height="48" alt="">
</div>
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
index 94063c92d..e7d2aecad 100644
--- a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
+++ b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
@@ -8,7 +8,7 @@
<button type="submit" class="collapse">Remote follow</button>
</form>
<%= raw Formatter.emojify(@user.name, emoji_for_user(@user)) %> |
- <%= link "@#{@user.nickname}@#{Endpoint.host()}", to: User.profile_url(@user) %>
+ <%= link "@#{@user.nickname}@#{Endpoint.host()}", to: (@user.uri || @user.ap_id) %>
</h3>
<p><%= raw @user.bio %></p>
</header>
diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex
index 43a81c75d..7ffd0e51b 100644
--- a/lib/pleroma/web/web_finger/web_finger.ex
+++ b/lib/pleroma/web/web_finger/web_finger.ex
@@ -173,7 +173,8 @@ defmodule Pleroma.Web.WebFinger do
get_template_from_xml(body)
else
_ ->
- with {:ok, %{body: body}} <- HTTP.get("https://#{domain}/.well-known/host-meta", []) do
+ with {:ok, %{body: body, status: status}} when status in 200..299 <-
+ HTTP.get("https://#{domain}/.well-known/host-meta", []) do
get_template_from_xml(body)
else
e -> {:error, "Can't find LRDD template: #{inspect(e)}"}
@@ -205,7 +206,7 @@ defmodule Pleroma.Web.WebFinger do
with response <-
HTTP.get(
address,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
),
{:ok, %{status: status, body: body}} when status in 200..299 <- response do
doc = XML.parse_document(body)