+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ActivityPub.Transmogrifier do
@moduledoc """
A module to handle coding from internal to wire ActivityPub and back.
"""
- alias Pleroma.User
- alias Pleroma.Object
alias Pleroma.Activity
+ alias Pleroma.Object
alias Pleroma.Repo
+ alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.ActivityPub.Visibility
import Ecto.Query
end
end
- def fix_addressing(map) do
- map
+ def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, explicit_mentions) do
+ explicit_to =
+ to
+ |> Enum.filter(fn x -> x in explicit_mentions end)
+
+ explicit_cc =
+ to
+ |> Enum.filter(fn x -> x not in explicit_mentions end)
+
+ final_cc =
+ (cc ++ explicit_cc)
+ |> Enum.uniq()
+
+ object
+ |> Map.put("to", explicit_to)
+ |> Map.put("cc", final_cc)
+ end
+
+ def fix_explicit_addressing(object, _explicit_mentions), do: object
+
+ # if directMessage flag is set to true, leave the addressing alone
+ def fix_explicit_addressing(%{"directMessage" => true} = object), do: object
+
+ def fix_explicit_addressing(object) do
+ explicit_mentions =
+ object
+ |> Utils.determine_explicit_mentions()
+
+ explicit_mentions = explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public"]
+
+ object
+ |> fix_explicit_addressing(explicit_mentions)
+ end
+
+ def fix_addressing(object) do
+ object
|> fix_addressing_list("to")
|> fix_addressing_list("cc")
|> fix_addressing_list("bto")
|> fix_addressing_list("bcc")
+ |> fix_explicit_addressing
end
def fix_actor(%{"attributedTo" => actor} = object) do
|> Map.put("actor", get_actor(%{"actor" => actor}))
end
- def fix_likes(%{"likes" => likes} = object)
- when is_bitstring(likes) do
- # Check for standardisation
- # This is what Peertube does
- # curl -H 'Accept: application/activity+json' $likes | jq .totalItems
+ # Check for standardisation
+ # This is what Peertube does
+ # curl -H 'Accept: application/activity+json' $likes | jq .totalItems
+ # Prismo returns only an integer (count) as "likes"
+ def fix_likes(%{"likes" => likes} = object) when not is_map(likes) do
object
|> Map.put("likes", [])
|> Map.put("like_count", 0)
case fetch_obj_helper(in_reply_to_id) do
{:ok, replied_object} ->
with %Activity{} = activity <-
- Activity.get_create_activity_by_object_ap_id(replied_object.data["id"]) do
+ Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
object
|> Map.put("inReplyTo", replied_object.data["id"])
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
attachments =
attachment
|> Enum.map(fn data ->
- url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}]
- Map.put(data, "url", url)
+ media_type = data["mediaType"] || data["mimeType"]
+ href = data["url"] || data["href"]
+
+ url = [%{"type" => "Link", "mediaType" => media_type, "href" => href}]
+
+ data
+ |> Map.put("mediaType", media_type)
+ |> Map.put("url", url)
end)
object
|> Map.put("url", url["href"])
end
- def fix_url(%{"url" => url} = object) when is_list(url) do
+ def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do
+ first_element = Enum.at(url, 0)
+
+ link_element =
+ url
+ |> Enum.filter(fn x -> is_map(x) end)
+ |> Enum.filter(fn x -> x["mimeType"] == "text/html" end)
+ |> Enum.at(0)
+
+ object
+ |> Map.put("attachment", [first_element])
+ |> Map.put("url", link_element["href"])
+ end
+
+ def fix_url(%{"type" => object_type, "url" => url} = object)
+ when object_type != "Video" and is_list(url) do
first_element = Enum.at(url, 0)
url_string =
true -> ""
end
- if Map.get(object, "type") == "Video" do
- object
- |> Map.delete("url")
- |> Map.put("attachment", url_string)
- else
- object
- |> Map.put("url", url_string)
- end
+ object
+ |> Map.put("url", url_string)
end
def fix_url(object), do: object
|> Map.put("tag", combined)
end
+ def fix_tag(%{"tag" => %{} = tag} = object), do: Map.put(object, "tag", [tag])
+
def fix_tag(object), do: object
# content map usually only has one language so this will do for now.
Map.put(data, "actor", actor)
|> fix_addressing
- with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]),
+ with nil <- Activity.get_create_by_object_ap_id(object["id"]),
%User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
object = fix_object(data["object"])
additional:
Map.take(data, [
"cc",
+ "directMessage",
"id"
])
}
if not User.locked?(followed) do
ActivityPub.accept(%{
to: [follower.ap_id],
- actor: followed.ap_id,
+ actor: followed,
object: data,
local: true
})
ActivityPub.accept(%{
to: follow_activity.data["to"],
type: "Accept",
- actor: followed.ap_id,
+ actor: followed,
object: follow_activity.data["id"],
local: false
}) do
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
{:ok, activity} <-
- ActivityPub.accept(%{
+ ActivityPub.reject(%{
to: follow_activity.data["to"],
- type: "Accept",
- actor: followed.ap_id,
+ type: "Reject",
+ actor: followed,
object: follow_activity.data["id"],
local: false
}) do
with actor <- get_actor(data),
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
- {:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false) do
+ public <- Visibility.is_public?(data),
+ {:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do
{:ok, activity}
else
_e -> :error
if object = Object.normalize(id), do: {:ok, object}, else: nil
end
- def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do
- with false <- String.starts_with?(inReplyTo, "http"),
- {:ok, %{data: replied_to_object}} <- get_obj_helper(inReplyTo) do
- Map.put(object, "inReplyTo", replied_to_object["external_url"] || inReplyTo)
+ def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
+ with false <- String.starts_with?(in_reply_to, "http"),
+ {:ok, %{data: replied_to_object}} <- get_obj_helper(in_reply_to) do
+ Map.put(object, "inReplyTo", replied_to_object["external_url"] || in_reply_to)
else
_e -> object
end
|> add_mention_tags
|> add_emoji_tags
|> add_attributed_to
+ |> add_likes
|> prepare_attachments
|> set_conversation
|> set_reply_to_uri
# internal -> Mastodon
# """
- def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
+ def prepare_outgoing(%{"type" => "Create", "object" => object} = data) do
object =
object
|> prepare_object
def prepare_outgoing(%{"type" => _type} = data) do
data =
data
+ |> strip_internal_fields
|> maybe_fix_object_url
|> Map.merge(Utils.make_json_ld_header())
def add_hashtags(object) do
tags =
(object["tag"] || [])
- |> Enum.map(fn tag ->
- %{
- "href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}",
- "name" => "##{tag}",
- "type" => "Hashtag"
- }
+ |> Enum.map(fn
+ # Expand internal representation tags into AS2 tags.
+ tag when is_binary(tag) ->
+ %{
+ "href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}",
+ "name" => "##{tag}",
+ "type" => "Hashtag"
+ }
+
+ # Do not process tags which are already AS2 tag objects.
+ tag when is_map(tag) ->
+ tag
end)
object
end
def add_attributed_to(object) do
- attributedTo = object["attributedTo"] || object["actor"]
+ attributed_to = object["attributedTo"] || object["actor"]
+
+ object
+ |> Map.put("attributedTo", attributed_to)
+ end
+
+ def add_likes(%{"id" => id, "like_count" => likes} = object) do
+ likes = %{
+ "id" => "#{id}/likes",
+ "first" => "#{id}/likes?page=1",
+ "type" => "OrderedCollection",
+ "totalItems" => likes
+ }
+
+ object
+ |> Map.put("likes", likes)
+ end
+ def add_likes(object) do
object
- |> Map.put("attributedTo", attributedTo)
end
def prepare_attachments(object) do
defp strip_internal_fields(object) do
object
|> Map.drop([
- "likes",
"like_count",
"announcements",
"announcement_count",
"emoji",
- "context_id"
+ "context_id",
+ "deleted_activity_id"
])
end
maybe_retire_websub(user.ap_id)
- # Only do this for recent activties, don't go through the whole db.
- # Only look at the last 1000 activities.
- since = (Repo.aggregate(Activity, :max, :id) || 0) - 1_000
-
q =
from(
a in Activity,
where: ^old_follower_address in a.recipients,
- where: a.id > ^since,
update: [
set: [
recipients: