X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fweb%2Factivity_pub%2Ftransmogrifier.ex;h=2f04cc6ffb496fe4097508413892bbe58419ebd3;hb=e124a109c1897529b4b9eae563f550b9fb5dfb50;hp=1c60ef8f56047a3c16fc445d808b52612cab64b0;hpb=93ae1c8bd34dee8e789c83e88d3279c5958c4c2a;p=akkoma
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 1c60ef8f5..7081213e7 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
+# Copyright © 2017-2021 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier do
@@ -7,11 +7,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
A module to handle coding from internal to wire ActivityPub and back.
"""
alias Pleroma.Activity
- alias Pleroma.EarmarkRenderer
alias Pleroma.EctoType.ActivityPub.ObjectValidators
- alias Pleroma.FollowingRelationship
alias Pleroma.Maps
- alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Object.Containment
alias Pleroma.Repo
@@ -22,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.Federator
alias Pleroma.Workers.TransmogrifierWorker
@@ -35,19 +33,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"""
def fix_object(object, options \\ []) do
object
- |> strip_internal_fields
- |> fix_actor
- |> fix_url
- |> fix_attachments
- |> fix_context
+ |> strip_internal_fields()
+ |> fix_actor()
+ |> fix_url()
+ |> fix_attachments()
+ |> fix_context()
|> fix_in_reply_to(options)
- |> fix_emoji
- |> fix_tag
- |> fix_content_map
- |> fix_addressing
- |> fix_summary
- |> fix_type(options)
- |> fix_content
+ |> fix_quote_url(options)
+ |> fix_emoji()
+ |> fix_tag()
+ |> fix_content_map()
+ |> fix_addressing()
+ |> fix_summary()
end
def fix_summary(%{"summary" => nil} = object) do
@@ -62,29 +59,35 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_summary(object), do: Map.put(object, "summary", "")
def fix_addressing_list(map, field) do
+ addrs = map[field]
+
cond do
- is_binary(map[field]) ->
- Map.put(map, field, [map[field]])
+ is_list(addrs) ->
+ Map.put(map, field, Enum.filter(addrs, &is_binary/1))
- is_nil(map[field]) ->
- Map.put(map, field, [])
+ is_binary(addrs) ->
+ Map.put(map, field, [addrs])
true ->
- map
+ Map.put(map, field, [])
end
end
- def fix_explicit_addressing(
- %{"to" => to, "cc" => cc} = object,
- explicit_mentions,
- follower_collection
- ) do
- explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end)
+ # if directMessage flag is set to true, leave the addressing alone
+ def fix_explicit_addressing(%{"directMessage" => true} = object, _follower_collection),
+ do: object
+
+ def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, follower_collection) do
+ explicit_mentions =
+ Utils.determine_explicit_mentions(object) ++
+ [Pleroma.Constants.as_public(), follower_collection]
+ explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end)
explicit_cc = Enum.filter(to, fn x -> x not in explicit_mentions end)
final_cc =
(cc ++ explicit_cc)
+ |> Enum.filter(& &1)
|> Enum.reject(fn x -> String.ends_with?(x, "/followers") and x != follower_collection end)
|> Enum.uniq()
@@ -93,69 +96,28 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("cc", final_cc)
end
- def fix_explicit_addressing(object, _explicit_mentions, _followers_collection), 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 = Utils.determine_explicit_mentions(object)
-
- %User{follower_address: follower_collection} =
+ def fix_addressing(object) do
+ {:ok, %User{follower_address: follower_collection}} =
object
|> Containment.get_actor()
- |> User.get_cached_by_ap_id()
-
- explicit_mentions =
- explicit_mentions ++
- [
- Pleroma.Constants.as_public(),
- follower_collection
- ]
-
- fix_explicit_addressing(object, explicit_mentions, follower_collection)
- end
-
- # if as:Public is addressed, then make sure the followers collection is also addressed
- # so that the activities will be delivered to local users.
- def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do
- recipients = to ++ cc
-
- if followers_collection not in recipients do
- cond do
- Pleroma.Constants.as_public() in cc ->
- to = to ++ [followers_collection]
- Map.put(object, "to", to)
-
- Pleroma.Constants.as_public() in to ->
- cc = cc ++ [followers_collection]
- Map.put(object, "cc", cc)
-
- true ->
- object
- end
- else
- object
- end
- end
-
- def fix_implicit_addressing(object, _), do: object
-
- def fix_addressing(object) do
- {:ok, %User{} = user} = User.get_or_fetch_by_ap_id(object["actor"])
- followers_collection = User.ap_followers(user)
+ |> User.get_or_fetch_by_ap_id()
object
|> fix_addressing_list("to")
|> fix_addressing_list("cc")
|> fix_addressing_list("bto")
|> fix_addressing_list("bcc")
- |> fix_explicit_addressing()
- |> fix_implicit_addressing(followers_collection)
+ |> fix_explicit_addressing(follower_collection)
+ |> CommonFixes.fix_implicit_addressing(follower_collection)
end
def fix_actor(%{"attributedTo" => actor} = object) do
- Map.put(object, "actor", Containment.get_actor(%{"actor" => actor}))
+ actor = Containment.get_actor(%{"actor" => actor})
+
+ # TODO: Remove actor field for Objects
+ object
+ |> Map.put("actor", actor)
+ |> Map.put("attributedTo", actor)
end
def fix_in_reply_to(object, options \\ [])
@@ -163,7 +125,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
when not is_nil(in_reply_to) do
in_reply_to_id = prepare_in_reply_to(in_reply_to)
- object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
depth = (options[:depth] || 0) + 1
if Federator.allowed_thread_distance?(depth) do
@@ -171,12 +132,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%Activity{} <- 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)
|> Map.put("context", replied_object.data["context"] || object["conversation"])
- |> Map.drop(["conversation"])
+ |> Map.drop(["conversation", "inReplyToAtomUri"])
else
e ->
- Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
+ Logger.warn("Couldn't fetch reply@#{inspect(in_reply_to_id)}, error: #{inspect(e)}")
object
end
else
@@ -186,6 +146,53 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_in_reply_to(object, _options), do: object
+ def fix_quote_url(object, options \\ [])
+
+ def fix_quote_url(%{"quoteUri" => quote_url} = object, options)
+ when not is_nil(quote_url) do
+ depth = (options[:depth] || 0) + 1
+
+ if Federator.allowed_thread_distance?(depth) do
+ with {:ok, quoted_object} <- get_obj_helper(quote_url, options),
+ %Activity{} <- Activity.get_create_by_object_ap_id(quoted_object.data["id"]) do
+ object
+ |> Map.put("quoteUri", quoted_object.data["id"])
+ else
+ e ->
+ Logger.warn("Couldn't fetch quote@#{inspect(quote_url)}, error: #{inspect(e)}")
+ object
+ end
+ else
+ object
+ end
+ end
+
+ # Soapbox
+ def fix_quote_url(%{"quoteUrl" => quote_url} = object, options) do
+ object
+ |> Map.put("quoteUri", quote_url)
+ |> Map.delete("quoteUrl")
+ |> fix_quote_url(options)
+ end
+
+ # Old Fedibird (bug)
+ # https://github.com/fedibird/mastodon/issues/9
+ def fix_quote_url(%{"quoteURL" => quote_url} = object, options) do
+ object
+ |> Map.put("quoteUri", quote_url)
+ |> Map.delete("quoteURL")
+ |> fix_quote_url(options)
+ end
+
+ def fix_quote_url(%{"_misskey_quote" => quote_url} = object, options) do
+ object
+ |> Map.put("quoteUri", quote_url)
+ |> Map.delete("_misskey_quote")
+ |> fix_quote_url(options)
+ end
+
+ def fix_quote_url(object, _), do: object
+
defp prepare_in_reply_to(in_reply_to) do
cond do
is_bitstring(in_reply_to) ->
@@ -222,10 +229,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
media_type =
cond do
- is_map(url) && MIME.valid?(url["mediaType"]) -> url["mediaType"]
- MIME.valid?(data["mediaType"]) -> data["mediaType"]
- MIME.valid?(data["mimeType"]) -> data["mimeType"]
- true -> nil
+ is_map(url) && MIME.extensions(url["mediaType"]) != [] ->
+ url["mediaType"]
+
+ is_bitstring(data["mediaType"]) && MIME.extensions(data["mediaType"]) != [] ->
+ data["mediaType"]
+
+ is_bitstring(data["mimeType"]) && MIME.extensions(data["mimeType"]) != [] ->
+ data["mimeType"]
+
+ true ->
+ nil
end
href =
@@ -233,18 +247,31 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
is_map(url) && is_binary(url["href"]) -> url["href"]
is_binary(data["url"]) -> data["url"]
is_binary(data["href"]) -> data["href"]
+ true -> nil
end
- attachment_url =
- %{"href" => href}
- |> Maps.put_if_present("mediaType", media_type)
- |> Maps.put_if_present("type", Map.get(url || %{}, "type"))
+ if href do
+ attachment_url =
+ %{
+ "href" => href,
+ "type" => Map.get(url || %{}, "type", "Link")
+ }
+ |> Maps.put_if_present("mediaType", media_type)
+ |> Maps.put_if_present("width", (url || %{})["width"] || data["width"])
+ |> Maps.put_if_present("height", (url || %{})["height"] || data["height"])
- %{"url" => [attachment_url]}
- |> Maps.put_if_present("mediaType", media_type)
- |> Maps.put_if_present("type", data["type"])
- |> Maps.put_if_present("name", data["name"])
+ %{
+ "url" => [attachment_url],
+ "type" => data["type"] || "Document"
+ }
+ |> Maps.put_if_present("mediaType", media_type)
+ |> Maps.put_if_present("name", data["name"])
+ |> Maps.put_if_present("blurhash", data["blurhash"])
+ else
+ nil
+ end
end)
+ |> Enum.filter(& &1)
Map.put(object, "attachment", attachments)
end
@@ -261,19 +288,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "url", url["href"])
end
- 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)
-
- 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
+ def fix_url(%{"url" => url} = object) when is_list(url) do
first_element = Enum.at(url, 0)
url_string =
@@ -291,16 +306,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do
emoji =
tags
- |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
+ |> Enum.filter(fn data -> is_map(data) and data["type"] == "Emoji" and data["icon"] end)
|> Enum.reduce(%{}, fn data, mapping ->
name = String.trim(data["name"], ":")
Map.put(mapping, name, data["icon"]["url"])
end)
- # we merge mastodon and pleroma emoji into a single mapping, to allow for both wire formats
- emoji = Map.merge(object["emoji"] || %{}, emoji)
-
Map.put(object, "emoji", emoji)
end
@@ -317,82 +329,49 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
tags =
tag
|> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
- |> Enum.map(fn data -> String.slice(data["name"], 1..-1) end)
+ |> Enum.map(fn
+ %{"name" => "#" <> hashtag} -> String.downcase(hashtag)
+ %{"name" => hashtag} -> String.downcase(hashtag)
+ end)
Map.put(object, "tag", tag ++ tags)
end
- def fix_tag(%{"tag" => %{"type" => "Hashtag", "name" => hashtag} = tag} = object) do
- combined = [tag, String.slice(hashtag, 1..-1)]
-
- Map.put(object, "tag", combined)
+ def fix_tag(%{"tag" => %{} = tag} = object) do
+ object
+ |> Map.put("tag", [tag])
+ |> fix_tag
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.
- def fix_content_map(%{"contentMap" => content_map} = object) do
+ def fix_content_map(%{"contentMap" => content_map} = object) when is_map(content_map) do
content_groups = Map.to_list(content_map)
- {_, content} = Enum.at(content_groups, 0)
- Map.put(object, "content", content)
+ if Enum.empty?(content_groups) do
+ object
+ else
+ {_, content} = Enum.at(content_groups, 0)
+
+ Map.put(object, "content", content)
+ end
end
def fix_content_map(object), do: object
- def fix_type(object, options \\ [])
+ defp fix_type(%{"type" => "Note", "inReplyTo" => reply_id, "name" => _} = object, options)
+ when is_binary(reply_id) do
+ options = Keyword.put(options, :fetch, true)
- def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
- when is_binary(reply_id) do
- with true <- Federator.allowed_thread_distance?(options[:depth]),
- {:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do
+ with %Object{data: %{"type" => "Question"}} <- Object.normalize(reply_id, options) do
Map.put(object, "type", "Answer")
else
_ -> object
end
end
- def fix_type(object, _), do: object
-
- defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = object)
- when is_binary(content) do
- html_content =
- content
- |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer})
- |> Pleroma.HTML.filter_tags()
-
- Map.merge(object, %{"content" => html_content, "mediaType" => "text/html"})
- end
-
- defp fix_content(object), do: object
-
- defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do
- with true <- id =~ "follows",
- %User{local: true} = follower <- User.get_cached_by_ap_id(follower_id),
- %Activity{} = activity <- Utils.fetch_latest_follow(follower, followed) do
- {:ok, activity}
- else
- _ -> {:error, nil}
- end
- end
-
- defp mastodon_follow_hack(_, _), do: {:error, nil}
-
- defp get_follow_activity(follow_object, followed) do
- with object_id when not is_nil(object_id) <- Utils.get_ap_id(follow_object),
- {_, %Activity{} = activity} <- {:activity, Activity.get_by_ap_id(object_id)} do
- {:ok, activity}
- else
- # Can't find the activity. This might a Mastodon 2.3 "Accept"
- {:activity, nil} ->
- mastodon_follow_hack(follow_object, followed)
-
- _ ->
- {:error, nil}
- end
- end
+ defp fix_type(object, _options), do: object
# Reduce the object list to find the reported user.
defp get_reported(objects) do
@@ -436,283 +415,81 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8,
do: :error
- # TODO: validate those with a Ecto scheme
- # - tags
- # - emoji
+ @doc "Rewrite misskey likes into EmojiReacts"
def handle_incoming(
- %{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
+ %{
+ "type" => "Like",
+ "content" => reaction
+ } = data,
options
- )
- when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer", "Audio"] do
- actor = Containment.get_actor(data)
-
- data =
- Map.put(data, "actor", actor)
- |> fix_addressing
-
- with nil <- Activity.get_create_by_object_ap_id(object["id"]),
- {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
- object = fix_object(object, options)
-
- params = %{
- to: data["to"],
- object: object,
- actor: user,
- context: object["context"],
- local: false,
- published: data["published"],
- additional:
- Map.take(data, [
- "cc",
- "directMessage",
- "id"
- ])
- }
-
- with {:ok, created_activity} <- ActivityPub.create(params) do
- reply_depth = (options[:depth] || 0) + 1
-
- if Federator.allowed_thread_distance?(reply_depth) do
- for reply_id <- replies(object) do
- Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{
- "id" => reply_id,
- "depth" => reply_depth
- })
- end
- end
-
- {:ok, created_activity}
- end
+ ) do
+ if Pleroma.Emoji.is_unicode_emoji?(reaction) or Pleroma.Emoji.matches_shortcode?(reaction) do
+ data
+ |> Map.put("type", "EmojiReact")
+ |> handle_incoming(options)
else
- %Activity{} = activity -> {:ok, activity}
- _e -> :error
+ data
+ |> Map.delete("content")
+ |> handle_incoming(options)
end
end
def handle_incoming(
- %{"type" => "Listen", "object" => %{"type" => "Audio"} = object} = data,
+ %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = 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"])
- }
+ )
+ when objtype in ~w{Question Answer Audio Video Event Article Note Page} do
+ fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
- ActivityPub.listen(params)
- else
- _e -> :error
- end
- end
+ object =
+ data["object"]
+ |> strip_internal_fields()
+ |> fix_type(fetch_options)
+ |> fix_in_reply_to(fetch_options)
+ |> fix_quote_url(fetch_options)
- def handle_incoming(
- %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
- _options
- ) do
- with %User{local: true} = followed <-
- User.get_cached_by_ap_id(Containment.get_actor(%{"actor" => followed})),
- {:ok, %User{} = follower} <-
- User.get_or_fetch_by_ap_id(Containment.get_actor(%{"actor" => follower})),
- {:ok, activity} <-
- ActivityPub.follow(follower, followed, id, false, skip_notify_and_stream: true) do
- with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
- {_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
- {_, false} <- {:user_locked, User.locked?(followed)},
- {_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
- {_, {:ok, _}} <-
- {:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
- {:ok, _relationship} <-
- FollowingRelationship.update(follower, followed, :follow_accept) do
- ActivityPub.accept(%{
- to: [follower.ap_id],
- actor: followed,
- object: data,
- local: true
- })
+ # Only change the Create's context if the object's context has been modified.
+ data =
+ if data["object"]["context"] != object["context"] do
+ data
+ |> Map.put("object", object)
+ |> Map.put("context", object["context"])
else
- {:user_blocked, true} ->
- {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
- {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
-
- ActivityPub.reject(%{
- to: [follower.ap_id],
- actor: followed,
- object: data,
- local: true
- })
-
- {:follow, {:error, _}} ->
- {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
- {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
-
- ActivityPub.reject(%{
- to: [follower.ap_id],
- actor: followed,
- object: data,
- local: true
- })
-
- {:user_locked, true} ->
- {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending)
- :noop
+ Map.put(data, "object", object)
end
- ActivityPub.notify_and_stream(activity)
- {:ok, activity}
- else
- _e ->
- :error
- end
- end
-
- def handle_incoming(
- %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => id} = data,
- _options
- ) do
- with actor <- Containment.get_actor(data),
- {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
- {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
- {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
- %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
- {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do
- User.update_follower_count(followed)
- User.update_following_count(follower)
-
- Notification.update_notification_type(followed, follow_activity)
-
- ActivityPub.accept(%{
- to: follow_activity.data["to"],
- type: "Accept",
- actor: followed,
- object: follow_activity.data["id"],
- local: false,
- activity_id: id
- })
- else
- _e ->
- :error
- end
- end
-
- def handle_incoming(
- %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => id} = data,
- _options
- ) do
- with actor <- Containment.get_actor(data),
- {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
- {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
- {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
- %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
- {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
- {:ok, activity} <-
- ActivityPub.reject(%{
- to: follow_activity.data["to"],
- type: "Reject",
- actor: followed,
- object: follow_activity.data["id"],
- local: false,
- activity_id: id
- }) do
- {:ok, activity}
- else
- _e -> :error
- end
- end
-
- @misskey_reactions %{
- "like" => "ð",
- "love" => "â¤ï¸",
- "laugh" => "ð",
- "hmm" => "ð¤",
- "surprise" => "ð®",
- "congrats" => "ð",
- "angry" => "ð¢",
- "confused" => "ð¥",
- "rip" => "ð",
- "pudding" => "ð®",
- "star" => "â"
- }
-
- @doc "Rewrite misskey likes into EmojiReacts"
- def handle_incoming(
- %{
- "type" => "Like",
- "_misskey_reaction" => reaction
- } = data,
- options
- ) do
- data
- |> Map.put("type", "EmojiReact")
- |> Map.put("content", @misskey_reactions[reaction] || reaction)
- |> handle_incoming(options)
- end
+ options = Keyword.put(options, :local, false)
- def handle_incoming(
- %{"type" => "Create", "object" => %{"type" => "ChatMessage"}} = data,
- _options
- ) do
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
- {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
+ nil <- Activity.get_create_by_object_ap_id(obj_id),
+ {:ok, activity, _} <- Pipeline.common_pipeline(data, options) do
{:ok, activity}
+ else
+ %Activity{} = activity -> {:ok, activity}
+ e -> e
end
end
def handle_incoming(%{"type" => type} = data, _options)
- when type in ["Like", "EmojiReact", "Announce"] do
+ when type in ~w{Like EmojiReact Announce Add Remove} do
with :ok <- ObjectValidator.fetch_actor_and_object(data),
- {:ok, activity, _meta} <-
- Pipeline.common_pipeline(data, local: false) do
+ {:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
else
- e -> {:error, e}
+ e ->
+ {:error, e}
end
end
def handle_incoming(
- %{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} =
- data,
+ %{"type" => type} = data,
_options
)
- when object_type in [
- "Person",
- "Application",
- "Service",
- "Organization"
- ] do
- with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
- {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
-
- actor
- |> User.remote_user_changeset(new_user_data)
- |> User.update_and_set_cache()
-
- ActivityPub.update(%{
- local: false,
- to: data["to"] || [],
- cc: data["cc"] || [],
- object: object,
- actor: actor_id,
- activity_id: data["id"]
- })
- else
- e ->
- Logger.error(e)
- :error
+ when type in ~w{Update Block Follow Accept Reject} do
+ with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
+ {:ok, activity, _} <-
+ Pipeline.common_pipeline(data, local: false) do
+ {:ok, activity}
end
end
@@ -720,10 +497,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Delete"} = data,
_options
) do
- with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
+ with {:ok, activity, _} <-
+ Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
else
- {:error, {:validate_object, _}} = e ->
+ {:error, {:validate, _}} = e ->
# Check if we have a create activity for this
with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]),
%Activity{data: %{"actor" => actor}} <-
@@ -788,21 +566,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(
- %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
- _options
- ) do
- with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
- {:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
- {:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
- User.unfollow(blocker, blocked)
- User.block(blocker, blocked)
- {:ok, activity}
- else
- _e -> :error
- end
- end
-
def handle_incoming(
%{
"type" => "Move",
@@ -825,7 +588,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
def get_obj_helper(id, options \\ []) do
- case Object.normalize(id, true, options) do
+ options = Keyword.put(options, :fetch, true)
+
+ case Object.normalize(id, options) do
%Object{} = object -> {:ok, object}
_ -> nil
end
@@ -844,7 +609,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"actor" => attributed_to,
"object" => data
}) do
- {:ok, Object.normalize(activity)}
+ {:ok, Object.normalize(activity, fetch: false)}
else
_ -> get_obj_helper(object_id)
end
@@ -865,6 +630,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def set_reply_to_uri(obj), do: obj
+ def set_quote_url(%{"quoteUri" => quote} = object) when is_binary(quote) do
+ Map.put(object, "quoteUrl", quote)
+ end
+
+ def set_quote_url(obj), do: obj
+
@doc """
Serialized Mastodon-compatible `replies` collection containing _self-replies_.
Based on Mastodon's ActivityPub::NoteSerializer#replies.
@@ -912,7 +683,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# Prepares the object of an outgoing create activity.
def prepare_object(object) do
object
- |> set_sensitive
|> add_hashtags
|> add_mention_tags
|> add_emoji_tags
@@ -920,10 +690,29 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> prepare_attachments
|> set_conversation
|> set_reply_to_uri
+ |> set_quote_url()
|> set_replies
|> strip_internal_fields
|> strip_internal_tags
|> set_type
+ |> maybe_process_history
+ end
+
+ defp maybe_process_history(%{"formerRepresentations" => %{"orderedItems" => history}} = object) do
+ processed_history =
+ Enum.map(
+ history,
+ fn
+ item when is_map(item) -> prepare_object(item)
+ item -> item
+ end
+ )
+
+ put_in(object, ["formerRepresentations", "orderedItems"], processed_history)
+ end
+
+ defp maybe_process_history(object) do
+ object
end
# @doc
@@ -932,10 +721,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# """
def prepare_outgoing(%{"type" => activity_type, "object" => object_id} = data)
- when activity_type in ["Create", "Listen"] do
+ when activity_type in ["Create"] do
object =
object_id
- |> Object.normalize()
+ |> Object.normalize(fetch: false)
|> Map.get(:data)
|> prepare_object
@@ -948,10 +737,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:ok, data}
end
+ def prepare_outgoing(%{"type" => "Update", "object" => %{"type" => objtype} = object} = data)
+ when objtype in Pleroma.Constants.updatable_object_types() do
+ object =
+ object
+ |> prepare_object
+
+ data =
+ data
+ |> Map.put("object", object)
+ |> Map.merge(Utils.make_json_ld_header())
+ |> Map.delete("bcc")
+
+ {:ok, data}
+ end
+
def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do
object =
object_id
- |> Object.normalize()
+ |> Object.normalize(fetch: false)
data =
if Visibility.is_private?(object) && object.data["actor"] == ap_id do
@@ -1025,7 +829,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(data, "object", external_url)
else
{:fetch, e} ->
- Logger.error("Couldn't fetch #{object} #{inspect(e)}")
+ Logger.error("Couldn't fetch fixed_object@#{object} #{inspect(e)}")
data
_ ->
@@ -1091,7 +895,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
defp build_emoji_tag({name, url}) do
%{
- "icon" => %{"url" => url, "type" => "Image"},
+ "icon" => %{"url" => "#{URI.encode(url)}", "type" => "Image"},
"name" => ":" <> name <> ":",
"type" => "Emoji",
"updated" => "1970-01-01T00:00:00Z",
@@ -1103,15 +907,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "conversation", object["context"])
end
- def set_sensitive(%{"sensitive" => true} = object) do
- object
- end
-
- def set_sensitive(object) do
- tags = object["tag"] || []
- Map.put(object, "sensitive", "nsfw" in tags)
- end
-
def set_type(%{"type" => "Answer"} = object) do
Map.put(object, "type", "Note")
end
@@ -1123,15 +918,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "attributedTo", attributed_to)
end
- # TODO: Revisit this
- def prepare_attachments(%{"type" => "ChatMessage"} = object), do: object
-
def prepare_attachments(object) do
attachments =
object
|> Map.get("attachment", [])
|> Enum.map(fn data ->
- [%{"mediaType" => media_type, "href" => href} | _] = data["url"]
+ [%{"mediaType" => media_type, "href" => href} = url | _] = data["url"]
%{
"url" => href,
@@ -1139,6 +931,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"name" => data["name"],
"type" => "Document"
}
+ |> Maps.put_if_present("width", url["width"])
+ |> Maps.put_if_present("height", url["height"])
+ |> Maps.put_if_present("blurhash", data["blurhash"])
end)
Map.put(object, "attachment", attachments)
@@ -1182,6 +977,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
{:ok, user} <- update_user(user, data) do
+ {:ok, _pid} = Task.start(fn -> ActivityPub.pinned_fetch_task(user) end)
TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
{:ok, user}
else