From 21f7e5e69c78be05749eb19f361e4cafcfc67955 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 21 Jul 2021 20:21:44 +0200 Subject: Ingestion Pipeline: Listen --- lib/pleroma/web/activity_pub/activity_pub.ex | 20 ------ lib/pleroma/web/activity_pub/builder.ex | 14 ++++ lib/pleroma/web/activity_pub/object_validator.ex | 14 ++++ .../object_validators/listen_validator.ex | 82 ++++++++++++++++++++++ lib/pleroma/web/activity_pub/side_effects.ex | 12 ++++ lib/pleroma/web/activity_pub/transmogrifier.ex | 59 +++++++--------- lib/pleroma/web/activity_pub/utils.ex | 26 +------ lib/pleroma/web/common_api.ex | 10 ++- lib/pleroma/web/pleroma_api/views/scrobble_view.ex | 32 ++++++--- 9 files changed, 178 insertions(+), 91 deletions(-) create mode 100644 lib/pleroma/web/activity_pub/object_validators/listen_validator.ex (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 064f93b22..8f009d6fd 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -314,26 +314,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do :ok end - @spec listen(map()) :: {:ok, Activity.t()} | {:error, any()} - def listen(%{to: to, actor: actor, context: context, object: object} = params) do - additional = params[:additional] || %{} - # only accept false as false value - local = !(params[:local] == false) - published = params[:published] - - listen_data = - make_listen_data( - %{to: to, actor: actor, published: published, context: context, object: object}, - additional - ) - - with {:ok, activity} <- insert(listen_data, local), - _ <- notify_and_stream(activity), - :ok <- maybe_federate(activity) do - {:ok, activity} - end - end - @spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) :: {:ok, Activity.t()} | nil | {:error, any()} def unfollow(follower, followed, activity_id \\ nil, local \\ true) do diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 5b25138a4..cf50cef73 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -337,4 +337,18 @@ defmodule Pleroma.Web.ActivityPub.Builder do defp pinned_url(nickname) when is_binary(nickname) do Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname) end + + def listen(%{to: to, actor: %{ap_id: actor}, object: object} = params, additional) do + {:ok, + %{ + "type" => "Listen", + "id" => Utils.generate_activity_id(), + "to" => to |> Enum.uniq(), + "actor" => actor, + "object" => object, + "published" => Map.get(params, :published, Utils.make_date()), + "context" => params.context + } + |> Map.merge(additional), []} + end end diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index c2afd6370..de5b8e522 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -31,6 +31,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Web.ActivityPub.ObjectValidators.EventValidator alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.ListenValidator alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator @@ -98,6 +99,19 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do end end + def validate( + %{"type" => "Listen", "object" => %{"type" => "Audio"} = object} = activity, + meta + ) do + with {:ok, activity} <- + activity + |> ListenValidator.cast_and_validate(meta) + |> Ecto.Changeset.apply_action(:insert) do + activity = stringify_keys(activity) + {:ok, activity, meta} + end + end + def validate( %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity, meta diff --git a/lib/pleroma/web/activity_pub/object_validators/listen_validator.ex b/lib/pleroma/web/activity_pub/object_validators/listen_validator.ex new file mode 100644 index 000000000..0c5e6819d --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/listen_validator.ex @@ -0,0 +1,82 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.ListenValidator do + use Ecto.Schema + + alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + + import Ecto.Changeset + + @primary_key false + @derive Jason.Encoder + + embedded_schema do + quote do + unquote do + import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields + message_fields() + end + end + + field(:actor, ObjectValidators.ObjectID) + field(:published, ObjectValidators.DateTime) + + embeds_one :object, UrlObjectValidator, primary_key: false do + field(:type, :string) + + field(:to, ObjectValidators.Recipients, default: []) + field(:cc, ObjectValidators.Recipients, default: []) + field(:bto, ObjectValidators.Recipients, default: []) + field(:bcc, ObjectValidators.Recipients, default: []) + + field(:title, :string) + field(:artist, :string) + field(:album, :string) + field(:length, :integer) + end + end + + def changeset(struct, data) do + struct + |> cast(data, __schema__(:fields) -- [:object]) + |> cast_embed(:object, with: &audio_changeset/2) + end + + def audio_changeset(struct, data) do + struct + |> cast(data, Map.keys(struct) -- [:__struct__]) + |> validate_inclusion(:type, ["Audio"]) + end + + def cast_data(data, meta \\ []) do + data = fix(data, meta) + + %__MODULE__{} + |> changeset(data) + end + + def cast_and_validate(data, meta \\ []) do + data + |> cast_data(meta) + |> validate_data(meta) + end + + defp fix(data, _meta) do + data + |> CommonFixes.fix_actor() + |> CommonFixes.fix_activity_addressing() + end + + defp validate_data(data_cng, _meta) do + # TODO: Restrict to Audio objects + + data_cng + |> validate_inclusion(:type, ["Listen"]) + |> validate_required([:id, :type, :object, :actor, :to, :cc, :published]) + |> CommonValidations.validate_actor_presence() + end +end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index b997c15db..ce0f61da2 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -234,6 +234,18 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do end end + # Tasks this handles + # - Actually create object + # - Rollback if we couldn't create it + @impl true + def handle(%{data: %{"type" => "Listen"}} = activity, meta) do + with {:ok, _object, meta} <- handle_object_creation(meta[:object_data], activity, meta) do + {:ok, activity, meta} + else + e -> Repo.rollback(e) + end + end + # Tasks this handles: # - Add announce to object # - Set up notification diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index a70330f0e..e240b6bbe 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -384,37 +384,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do 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 - actor = Containment.get_actor(data) - - data = - Map.put(data, "actor", actor) - |> fix_addressing - - with {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do - reply_depth = (options[:depth] || 0) + 1 - options = Keyword.put(options, :depth, reply_depth) - object = fix_object(object, options) - - params = %{ - to: data["to"], - object: object, - actor: user, - context: nil, - local: false, - published: data["published"], - additional: Map.take(data, ["cc", "id"]) - } - - ActivityPub.listen(params) - else - _e -> :error - end - end - @misskey_reactions %{ "like" => "👍", "love" => "❤️", @@ -492,6 +461,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end + def handle_incoming( + %{"type" => "Listen", "object" => %{"type" => "Audio"}} = data, + _options + ) do + with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), + {:ok, activity, _} <- + Pipeline.common_pipeline(data, local: false) do + {:ok, activity} + end + end + def handle_incoming( %{"type" => "Delete"} = data, _options @@ -694,8 +674,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do # internal -> Mastodon # """ - def prepare_outgoing(%{"type" => activity_type, "object" => object_id} = data) - when activity_type in ["Create", "Listen"] do + def prepare_outgoing(%{"type" => "Create", "object" => object_id} = data) do object = object_id |> Object.normalize(fetch: false) @@ -711,6 +690,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do {:ok, data} end + def prepare_outgoing(%{"type" => "Listen", "object" => object} = data) do + object = + object + |> prepare_object + + data = + data + |> Map.put("object", object) + |> Map.merge(Utils.make_json_ld_header()) + |> Map.delete("bcc") + + {:ok, data} + end + def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do object = object_id diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 9cde7805c..392c2a8c9 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -23,16 +23,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do require Logger require Pleroma.Constants - @supported_object_types [ - "Article", - "Note", - "Event", - "Video", - "Page", - "Question", - "Answer", - "Audio" - ] + @supported_object_types ~w[Article Note Event Video Page Question Answer Audio] @strip_status_report_states ~w(closed resolved) @supported_report_states ~w(open closed resolved) @valid_visibilities ~w(public unlisted private direct) @@ -675,21 +666,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> Map.merge(additional) end - #### Listen-related helpers - def make_listen_data(params, additional) do - published = params.published || make_date() - - %{ - "type" => "Listen", - "to" => params.to |> Enum.uniq(), - "actor" => params.actor.ap_id, - "object" => params.object, - "published" => published, - "context" => params.context - } - |> Map.merge(additional) - end - #### Flag-related helpers @spec make_flag_data(map(), map()) :: map() def make_flag_data(%{actor: actor, context: context, content: content} = params, additional) do diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 1b95ee89c..d5a4e72fa 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -391,8 +391,14 @@ defmodule Pleroma.Web.CommonAPI do end def listen(user, data) do - with {:ok, draft} <- ActivityDraft.listen(user, data) do - ActivityPub.listen(draft.changes) + with {_, {:ok, %{changes: draft}}} <- {:draft, ActivityDraft.listen(user, data)}, + {_, {:ok, activity_data, []}} <- {:builder, Builder.listen(draft, draft.additional)}, + {_, {:ok, activity, _}} <- + {:pipeline, Pipeline.common_pipeline(activity_data, local: true)}, + {_, %Activity{} = activity} <- {:norm, Activity.normalize(activity)} do + {:ok, activity} + else + e -> {:error, e} end end diff --git a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex index a5985fb2a..dd2173b0b 100644 --- a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex +++ b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex @@ -9,25 +9,35 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleView do alias Pleroma.Activity alias Pleroma.HTML - alias Pleroma.Object alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.AccountView - def render("show.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do - object = Object.normalize(activity, fetch: false) - - user = CommonAPI.get_user(activity.data["actor"]) - created_at = Utils.to_masto_date(activity.data["published"]) + def render( + "show.json", + %{ + activity: %Activity{ + id: id, + data: %{ + "type" => "Listen", + "actor" => actor, + "published" => published, + "object" => object + } + } + } = opts + ) do + user = CommonAPI.get_user(actor) + created_at = Utils.to_masto_date(published) %{ - id: activity.id, + id: id, account: AccountView.render("show.json", %{user: user, for: opts[:for]}), created_at: created_at, - title: object.data["title"] |> HTML.strip_tags(), - artist: object.data["artist"] |> HTML.strip_tags(), - album: object.data["album"] |> HTML.strip_tags(), - length: object.data["length"] + title: object["title"] |> HTML.strip_tags(), + artist: object["artist"] |> HTML.strip_tags(), + album: object["album"] |> HTML.strip_tags(), + length: object["length"] } end -- cgit v1.2.3