diff options
| -rw-r--r-- | lib/pleroma/list.ex | 32 | ||||
| -rw-r--r-- | lib/pleroma/web/activity_pub/activity_pub.ex | 57 | ||||
| -rw-r--r-- | lib/pleroma/web/activity_pub/publisher.ex | 63 | ||||
| -rw-r--r-- | lib/pleroma/web/activity_pub/transmogrifier.ex | 5 | ||||
| -rw-r--r-- | lib/pleroma/web/common_api/common_api.ex | 34 | ||||
| -rw-r--r-- | lib/pleroma/web/common_api/utils.ex | 8 | ||||
| -rw-r--r-- | lib/pleroma/web/salmon/salmon.ex | 25 | 
7 files changed, 175 insertions, 49 deletions
| diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex index a5b1cad68..81b842e9c 100644 --- a/lib/pleroma/list.ex +++ b/lib/pleroma/list.ex @@ -12,6 +12,8 @@ defmodule Pleroma.List do    alias Pleroma.Repo    alias Pleroma.User +  @ap_id_regex ~r/^\/users\/(?<nickname>\w+)\/lists\/(?<list_id>\d+)/ +    schema "lists" do      belongs_to(:user, User, type: Pleroma.FlakeId)      field(:title, :string) @@ -32,6 +34,12 @@ defmodule Pleroma.List do      |> validate_required([:following])    end +  def ap_id(%User{nickname: nickname}, list_id) do +    Pleroma.Web.Endpoint.url() <> "/users/#{nickname}/lists/#{list_id}" +  end + +  def ap_id({nickname, list_id}), do: ap_id(%User{nickname: nickname}, list_id) +    def for_user(user, _opts) do      query =        from( @@ -55,6 +63,19 @@ defmodule Pleroma.List do      Repo.one(query)    end +  def get_by_ap_id(ap_id) do +    host = Pleroma.Web.Endpoint.host() + +    with %{host: ^host, path: path} <- URI.parse(ap_id), +         %{"list_id" => list_id, "nickname" => nickname} <- +           Regex.named_captures(@ap_id_regex, path), +         %User{} = user <- User.get_cached_by_nickname(nickname) do +      get(list_id, user) +    else +      _ -> nil +    end +  end +    def get_following(%Pleroma.List{following: following} = _list) do      q =        from( @@ -125,4 +146,15 @@ defmodule Pleroma.List do      |> follow_changeset(attrs)      |> Repo.update()    end + +  def memberships(%User{follower_address: follower_address}) do +    Pleroma.List +    |> where([l], ^follower_address in l.following) +    |> join(:inner, [l], u in User, on: l.user_id == u.id) +    |> select([l, u], {u.nickname, l.id}) +    |> Repo.all() +    |> Enum.map(&ap_id/1) +  end + +  def memberships(_), do: []  end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 11777c220..eba8f8018 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -25,19 +25,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    # For Announce activities, we filter the recipients based on following status for any actors    # that match actual users.  See issue #164 for more information about why this is necessary.    defp get_recipients(%{"type" => "Announce"} = data) do -    to = data["to"] || [] -    cc = data["cc"] || [] +    to = Map.get(data, "to", []) +    cc = Map.get(data, "cc", []) +    bcc = Map.get(data, "bcc", [])      actor = User.get_cached_by_ap_id(data["actor"])      recipients = -      (to ++ cc) -      |> Enum.filter(fn recipient -> +      Enum.filter(Enum.concat([to, cc, bcc]), fn recipient ->          case User.get_cached_by_ap_id(recipient) do -          nil -> -            true - -          user -> -            User.following?(user, actor) +          nil -> true +          user -> User.following?(user, actor)          end        end) @@ -45,17 +42,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    end    defp get_recipients(%{"type" => "Create"} = data) do -    to = data["to"] || [] -    cc = data["cc"] || [] -    actor = data["actor"] || [] -    recipients = (to ++ cc ++ [actor]) |> Enum.uniq() +    to = Map.get(data, "to", []) +    cc = Map.get(data, "cc", []) +    bcc = Map.get(data, "bcc", []) +    actor = Map.get(data, "actor", []) +    recipients = [to, cc, bcc, [actor]] |> Enum.concat() |> Enum.uniq()      {recipients, to, cc}    end    defp get_recipients(data) do -    to = data["to"] || [] -    cc = data["cc"] || [] -    recipients = to ++ cc +    to = Map.get(data, "to", []) +    cc = Map.get(data, "cc", []) +    bcc = Map.get(data, "bcc", []) +    recipients = Enum.concat([to, cc, bcc])      {recipients, to, cc}    end @@ -831,9 +830,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    defp maybe_order(query, _), do: query    def fetch_activities_query(recipients, opts \\ %{}) do -    base_query = from(activity in Activity) - -    base_query +    Activity      |> maybe_preload_objects(opts)      |> maybe_preload_bookmarks(opts)      |> maybe_order(opts) @@ -857,9 +854,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    end    def fetch_activities(recipients, opts \\ %{}) do -    fetch_activities_query(recipients, opts) +    list_memberships = Pleroma.List.memberships(opts["user"]) + +    fetch_activities_query(recipients ++ list_memberships, opts)      |> Pagination.fetch_paginated(opts)      |> Enum.reverse() +    |> maybe_update_cc(list_memberships, opts["user"]) +  end + +  defp maybe_update_cc(activities, [], _), do: activities +  defp maybe_update_cc(activities, _, nil), do: activities + +  defp maybe_update_cc(activities, list_memberships, user) do +    Enum.map(activities, fn +      %{data: %{"bcc" => bcc}} = activity when is_list(bcc) -> +        if Enum.any?(bcc, &(&1 in list_memberships)) do +          update_in(activity.data["cc"], &[user.ap_id | &1]) +        else +          activity +        end + +      activity -> +        activity +    end)    end    def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 8e3af0a81..036ac892e 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -93,18 +93,67 @@ defmodule Pleroma.Web.ActivityPub.Publisher do      end    end -  @doc """ -  Publishes an activity to all relevant peers. -  """ -  def publish(%User{} = actor, %Activity{} = activity) do -    remote_followers = +  defp recipients(actor, activity) do +    followers =        if actor.follower_address in activity.recipients do          {:ok, followers} = User.get_followers(actor) -        followers |> Enum.filter(&(!&1.local)) +        Enum.filter(followers, &(!&1.local))        else          []        end +    Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers +  end + +  defp get_cc_ap_ids(ap_id, recipients) do +    host = Map.get(URI.parse(ap_id), :host) + +    recipients +    |> Enum.filter(fn %User{ap_id: ap_id} -> Map.get(URI.parse(ap_id), :host) == host end) +    |> Enum.map(& &1.ap_id) +  end + +  @doc """ +  Publishes an activity with BCC to all relevant peers. +  """ + +  def publish(actor, %{data: %{"bcc" => bcc}} = activity) when is_list(bcc) and bcc != [] do +    public = is_public?(activity) +    {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) + +    recipients = recipients(actor, activity) + +    recipients +    |> Enum.filter(&User.ap_enabled?/1) +    |> Enum.map(fn %{info: %{source_data: data}} -> data["inbox"] end) +    |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) +    |> Instances.filter_reachable() +    |> Enum.each(fn {inbox, unreachable_since} -> +      %User{ap_id: ap_id} = +        Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end) + +      cc = get_cc_ap_ids(ap_id, recipients) + +      json = +        data +        |> Map.put("cc", cc) +        |> Map.put("directMessage", true) +        |> Jason.encode!() + +      Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{ +        inbox: inbox, +        json: json, +        actor: actor, +        id: activity.data["id"], +        unreachable_since: unreachable_since +      }) +    end) +  end + +  @doc """ +  Publishes an activity to all relevant peers. +  """ +  def publish(%User{} = actor, %Activity{} = activity) do      public = is_public?(activity)      if public && Config.get([:instance, :allow_relay]) do @@ -115,7 +164,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do      {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)      json = Jason.encode!(data) -    (Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers) +    recipients(actor, activity)      |> Enum.filter(fn user -> User.ap_enabled?(user) end)      |> Enum.map(fn %{info: %{source_data: data}} ->        (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"] diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 508f3532f..c4cc77a95 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -741,13 +741,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    def prepare_outgoing(%{"type" => "Create", "object" => object_id} = data) do      object = -      Object.normalize(object_id).data +      object_id +      |> Object.normalize() +      |> Map.get(:data)        |> prepare_object      data =        data        |> Map.put("object", object)        |> Map.merge(Utils.make_json_ld_header()) +      |> Map.delete("bcc")      {:ok, data}    end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index b53869c75..ed2c0017f 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -120,6 +120,10 @@ defmodule Pleroma.Web.CommonAPI do        when visibility in ~w{public unlisted private direct},        do: visibility +  def get_visibility(%{"visibility" => "list:" <> list_id}) do +    {:list, String.to_integer(list_id)} +  end +    def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do      case get_replied_to_activity(status_id) do        nil -> @@ -150,6 +154,7 @@ defmodule Pleroma.Web.CommonAPI do               visibility             ),           {to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility), +         bcc <- bcc_for_list(user, visibility),           context <- make_context(in_reply_to),           cw <- data["spoiler_text"] || "",           full_payload <- String.trim(status <> cw), @@ -172,19 +177,16 @@ defmodule Pleroma.Web.CommonAPI do               "emoji",               Formatter.get_emoji_map(full_payload)             ) do -      res = -        ActivityPub.create( -          %{ -            to: to, -            actor: user, -            context: context, -            object: object, -            additional: %{"cc" => cc, "directMessage" => visibility == "direct"} -          }, -          Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false -        ) - -      res +      ActivityPub.create( +        %{ +          to: to, +          actor: user, +          context: context, +          object: object, +          additional: %{"cc" => cc, "bcc" => bcc, "directMessage" => visibility == "direct"} +        }, +        Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false +      )      end    end @@ -193,7 +195,7 @@ defmodule Pleroma.Web.CommonAPI do      user =        with emoji <- emoji_from_profile(user),             source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji), -           info_cng <- Pleroma.User.Info.set_source_data(user.info, source_data), +           info_cng <- User.Info.set_source_data(user.info, source_data),             change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),             {:ok, user} <- User.update_and_set_cache(change) do          user @@ -226,7 +228,7 @@ defmodule Pleroma.Web.CommonAPI do           } = activity <- get_by_id_or_ap_id(id_or_ap_id),           true <- Enum.member?(object_to, "https://www.w3.org/ns/activitystreams#Public"),           %{valid?: true} = info_changeset <- -           Pleroma.User.Info.add_pinnned_activity(user.info, activity), +           User.Info.add_pinnned_activity(user.info, activity),           changeset <-             Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),           {:ok, _user} <- User.update_and_set_cache(changeset) do @@ -243,7 +245,7 @@ defmodule Pleroma.Web.CommonAPI do    def unpin(id_or_ap_id, user) do      with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),           %{valid?: true} = info_changeset <- -           Pleroma.User.Info.remove_pinnned_activity(user.info, activity), +           User.Info.remove_pinnned_activity(user.info, activity),           changeset <-             Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),           {:ok, _user} <- User.update_and_set_cache(changeset) do diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 1dfe50b40..f082b77d8 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -102,6 +102,14 @@ defmodule Pleroma.Web.CommonAPI.Utils do      end    end +  def to_for_user_and_mentions(_user, _mentions, _inReplyTo, _), do: {[], []} + +  def bcc_for_list(user, {:list, list_id}) do +    [Pleroma.List.ap_id(user, list_id)] +  end + +  def bcc_for_list(_, _), do: [] +    def make_content_html(          status,          attachments, diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index 42709ab47..80c3a3190 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -162,11 +162,26 @@ defmodule Pleroma.Web.Salmon do      {:ok, salmon}    end -  def remote_users(%{data: %{"to" => to} = data}) do -    to = to ++ (data["cc"] || []) +  def remote_users(%User{id: user_id}, %{data: %{"to" => to} = data}) do +    cc = Map.get(data, "cc", []) + +    bcc = +      data +      |> Map.get("bcc", []) +      |> Enum.reduce([], fn ap_id, bcc -> +        case Pleroma.List.get_by_ap_id(ap_id) do +          %Pleroma.List{user_id: ^user_id} = list -> +            {:ok, following} = Pleroma.List.get_following(list) +            bcc ++ Enum.map(following, & &1.ap_id) + +          _ -> +            bcc +        end +      end) -    to -    |> Enum.map(fn id -> User.get_cached_by_ap_id(id) end) +    [to, cc, bcc] +    |> Enum.concat() +    |> Enum.map(&User.get_cached_by_ap_id/1)      |> Enum.filter(fn user -> user && !user.local end)    end @@ -230,7 +245,7 @@ defmodule Pleroma.Web.Salmon do        {:ok, private, _} = keys_from_pem(keys)        {:ok, feed} = encode(private, feed) -      remote_users = remote_users(activity) +      remote_users = remote_users(user, activity)        salmon_urls = Enum.map(remote_users, & &1.info.salmon)        reachable_urls_metadata = Instances.filter_reachable(salmon_urls) | 
