summaryrefslogtreecommitdiff
path: root/lib/pleroma/web/activity_pub
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pleroma/web/activity_pub')
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex263
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex11
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex250
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex93
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex25
5 files changed, 445 insertions, 197 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 3a914088f..04b50c1cc 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -18,7 +18,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
with nil <- Activity.get_by_ap_id(map["id"]),
map <- lazy_put_activity_defaults(map),
:ok <- insert_full_object(map) do
- {:ok, activity} = Repo.insert(%Activity{data: map, local: local, actor: map["actor"], recipients: get_recipients(map)})
+ {:ok, activity} =
+ Repo.insert(%Activity{
+ data: map,
+ local: local,
+ actor: map["actor"],
+ recipients: get_recipients(map)
+ })
+
Notification.create_notifications(activity)
stream_out(activity)
{:ok, activity}
@@ -31,8 +38,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def stream_out(activity) do
if activity.data["type"] in ["Create", "Announce"] do
Pleroma.Web.Streamer.stream("user", activity)
+
if Enum.member?(activity.data["to"], "https://www.w3.org/ns/activitystreams#Public") do
Pleroma.Web.Streamer.stream("public", activity)
+
if activity.local do
Pleroma.Web.Streamer.stream("public:local", activity)
end
@@ -42,10 +51,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def create(%{to: to, actor: actor, context: context, object: object} = params) do
additional = params[:additional] || %{}
- local = !(params[:local] == false) # only accept false as false value
+ # only accept false as false value
+ local = !(params[:local] == false)
published = params[:published]
- with create_data <- make_create_data(%{to: to, actor: actor, published: published, context: context, object: object}, additional),
+ with create_data <-
+ make_create_data(
+ %{to: to, actor: actor, published: published, context: context, object: object},
+ additional
+ ),
{:ok, activity} <- insert(create_data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
@@ -53,7 +67,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def accept(%{to: to, actor: actor, object: object} = params) do
- local = !(params[:local] == false) # only accept false as false value
+ # only accept false as false value
+ local = !(params[:local] == false)
with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object},
{:ok, activity} <- insert(data, local),
@@ -63,9 +78,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
- local = !(params[:local] == false) # only accept false as false value
-
- with data <- %{"to" => to, "cc" => cc, "type" => "Update", "actor" => actor, "object" => object},
+ # only accept false as false value
+ local = !(params[:local] == false)
+
+ with data <- %{
+ "to" => to,
+ "cc" => cc,
+ "type" => "Update",
+ "actor" => actor,
+ "object" => object
+ },
{:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
@@ -73,7 +95,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
# TODO: This is weird, maybe we shouldn't check here if we can make the activity.
- def like(%User{ap_id: ap_id} = user, %Object{data: %{"id" => _}} = object, activity_id \\ nil, local \\ true) do
+ def like(
+ %User{ap_id: ap_id} = user,
+ %Object{data: %{"id" => _}} = object,
+ activity_id \\ nil,
+ local \\ true
+ ) do
with nil <- get_existing_like(ap_id, object),
like_data <- make_like_data(user, object, activity_id),
{:ok, activity} <- insert(like_data, local),
@@ -91,11 +118,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, _activity} <- Repo.delete(activity),
{:ok, object} <- remove_like_from_object(activity, object) do
{:ok, object}
- else _e -> {:ok, object}
+ else
+ _e -> {:ok, object}
end
end
- def announce(%User{ap_id: _} = user, %Object{data: %{"id" => _}} = object, activity_id \\ nil, local \\ true) do
+ def announce(
+ %User{ap_id: _} = user,
+ %Object{data: %{"id" => _}} = object,
+ activity_id \\ nil,
+ local \\ true
+ ) do
with true <- is_public?(object),
announce_data <- make_announce_data(user, object, activity_id),
{:ok, activity} <- insert(announce_data, local),
@@ -119,19 +152,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
unfollow_data <- make_unfollow_data(follower, followed, follow_activity),
{:ok, activity} <- insert(unfollow_data, local),
- :ok, maybe_federate(activity) do
+ :ok,
+ maybe_federate(activity) do
{:ok, activity}
end
end
def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
user = User.get_cached_by_ap_id(actor)
+
data = %{
"type" => "Delete",
"actor" => actor,
"object" => id,
"to" => [user.follower_address, "https://www.w3.org/ns/activitystreams#Public"]
}
+
with Repo.delete(object),
Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)),
{:ok, activity} <- insert(data, local),
@@ -142,112 +178,147 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def fetch_activities_for_context(context, opts \\ %{}) do
public = ["https://www.w3.org/ns/activitystreams#Public"]
- recipients = if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
- query = from activity in Activity
- query = query
+ recipients =
+ if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
+
+ query = from(activity in Activity)
+
+ query =
+ query
|> restrict_blocked(opts)
|> restrict_recipients(recipients, opts["user"])
- query = from activity in query,
- where: fragment("?->>'type' = ? and ?->>'context' = ?", activity.data, "Create", activity.data, ^context),
- order_by: [desc: :id]
+ query =
+ from(
+ activity in query,
+ where:
+ fragment(
+ "?->>'type' = ? and ?->>'context' = ?",
+ activity.data,
+ "Create",
+ activity.data,
+ ^context
+ ),
+ order_by: [desc: :id]
+ )
+
Repo.all(query)
end
# TODO: Make this work properly with unlisted.
def fetch_public_activities(opts \\ %{}) do
q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts)
+
q
- |> Repo.all
- |> Enum.reverse
+ |> Repo.all()
+ |> Enum.reverse()
end
defp restrict_since(query, %{"since_id" => since_id}) do
- from activity in query, where: activity.id > ^since_id
+ from(activity in query, where: activity.id > ^since_id)
end
+
defp restrict_since(query, _), do: query
defp restrict_tag(query, %{"tag" => tag}) do
- from activity in query,
+ from(
+ activity in query,
where: fragment("? <@ (? #> '{\"object\",\"tag\"}')", ^tag, activity.data)
+ )
end
+
defp restrict_tag(query, _), do: query
defp restrict_recipients(query, [], user), do: query
+
defp restrict_recipients(query, recipients, nil) do
- from activity in query,
- where: fragment("? && ?", ^recipients, activity.recipients)
+ from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
end
+
defp restrict_recipients(query, recipients, user) do
- from activity in query,
+ from(
+ activity in query,
where: fragment("? && ?", ^recipients, activity.recipients),
or_where: activity.actor == ^user.ap_id
+ )
end
defp restrict_limit(query, %{"limit" => limit}) do
- from activity in query,
- limit: ^limit
+ from(activity in query, limit: ^limit)
end
+
defp restrict_limit(query, _), do: query
defp restrict_local(query, %{"local_only" => true}) do
- from activity in query, where: activity.local == true
+ from(activity in query, where: activity.local == true)
end
+
defp restrict_local(query, _), do: query
defp restrict_max(query, %{"max_id" => max_id}) do
- from activity in query, where: activity.id < ^max_id
+ from(activity in query, where: activity.id < ^max_id)
end
+
defp restrict_max(query, _), do: query
defp restrict_actor(query, %{"actor_id" => actor_id}) do
- from activity in query,
- where: activity.actor == ^actor_id
+ from(activity in query, where: activity.actor == ^actor_id)
end
+
defp restrict_actor(query, _), do: query
defp restrict_type(query, %{"type" => type}) when is_binary(type) do
restrict_type(query, %{"type" => [type]})
end
+
defp restrict_type(query, %{"type" => type}) do
- from activity in query,
- where: fragment("?->>'type' = ANY(?)", activity.data, ^type)
+ from(activity in query, where: fragment("?->>'type' = ANY(?)", activity.data, ^type))
end
+
defp restrict_type(query, _), do: query
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
- from activity in query,
+ from(
+ activity in query,
where: fragment("? <@ (? #> '{\"object\",\"likes\"}')", ^ap_id, activity.data)
+ )
end
+
defp restrict_favorited_by(query, _), do: query
defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
- from activity in query,
+ from(
+ activity in query,
where: fragment("not (? #> '{\"object\",\"attachment\"}' = ?)", activity.data, ^[])
+ )
end
+
defp restrict_media(query, _), do: query
# Only search through last 100_000 activities by default
defp restrict_recent(query, %{"whole_db" => true}), do: query
+
defp restrict_recent(query, _) do
since = (Repo.aggregate(Activity, :max, :id) || 0) - 100_000
- from activity in query,
- where: activity.id > ^since
+ from(activity in query, where: activity.id > ^since)
end
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
blocks = info["blocks"] || []
- from activity in query,
- where: fragment("not (? = ANY(?))", activity.actor, ^blocks)
+ from(activity in query, where: fragment("not (? = ANY(?))", activity.actor, ^blocks))
end
+
defp restrict_blocked(query, _), do: query
def fetch_activities_query(recipients, opts \\ %{}) do
- base_query = from activity in Activity,
- limit: 20,
- order_by: [fragment("? desc nulls last", activity.id)]
+ base_query =
+ from(
+ activity in Activity,
+ limit: 20,
+ order_by: [fragment("? desc nulls last", activity.id)]
+ )
base_query
|> restrict_recipients(recipients, opts["user"])
@@ -266,8 +337,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def fetch_activities(recipients, opts \\ %{}) do
fetch_activities_query(recipients, opts)
- |> Repo.all
- |> Enum.reverse
+ |> Repo.all()
+ |> Enum.reverse()
end
def upload(file) do
@@ -276,15 +347,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def user_data_from_user_object(data) do
- avatar = data["icon"]["url"] && %{
- "type" => "Image",
- "url" => [%{"href" => data["icon"]["url"]}]
- }
-
- banner = data["image"]["url"] && %{
- "type" => "Image",
- "url" => [%{"href" => data["image"]["url"]}]
- }
+ avatar =
+ data["icon"]["url"] &&
+ %{
+ "type" => "Image",
+ "url" => [%{"href" => data["icon"]["url"]}]
+ }
+
+ banner =
+ data["image"]["url"] &&
+ %{
+ "type" => "Image",
+ "url" => [%{"href" => data["image"]["url"]}]
+ }
user_data = %{
ap_id: data["id"],
@@ -304,8 +379,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def fetch_and_prepare_user_from_ap_id(ap_id) do
- with {:ok, %{status_code: 200, body: body}} <- @httpoison.get(ap_id, ["Accept": "application/activity+json"]),
- {:ok, data} <- Jason.decode(body) do
+ with {:ok, %{status_code: 200, body: body}} <-
+ @httpoison.get(ap_id, Accept: "application/activity+json"),
+ {:ok, data} <- Jason.decode(body) do
user_data_from_user_object(data)
else
e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
@@ -333,32 +409,48 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def publish(actor, activity) do
- followers = if actor.follower_address in activity.recipients do
- {:ok, followers} = User.get_followers(actor)
- followers |> Enum.filter(&(!&1.local))
- else
- []
- end
+ followers =
+ if actor.follower_address in activity.recipients do
+ {:ok, followers} = User.get_followers(actor)
+ followers |> Enum.filter(&(!&1.local))
+ else
+ []
+ end
- remote_inboxes = (Pleroma.Web.Salmon.remote_users(activity) ++ followers)
- |> Enum.filter(fn (user) -> User.ap_enabled?(user) end)
- |> Enum.map(fn (%{info: %{"source_data" => data}}) ->
- (data["endpoints"] && data["endpoints"]["sharedInbox"]) || data["inbox"]
- end)
- |> Enum.uniq
+ remote_inboxes =
+ (Pleroma.Web.Salmon.remote_users(activity) ++ followers)
+ |> Enum.filter(fn user -> User.ap_enabled?(user) end)
+ |> Enum.map(fn %{info: %{"source_data" => data}} ->
+ (data["endpoints"] && data["endpoints"]["sharedInbox"]) || data["inbox"]
+ end)
+ |> Enum.uniq()
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
json = Jason.encode!(data)
- Enum.each remote_inboxes, fn(inbox) ->
- Federator.enqueue(:publish_single_ap, %{inbox: inbox, json: json, actor: actor, id: activity.data["id"]})
- end
+
+ Enum.each(remote_inboxes, fn inbox ->
+ Federator.enqueue(:publish_single_ap, %{
+ inbox: inbox,
+ json: json,
+ actor: actor,
+ id: activity.data["id"]
+ })
+ end)
end
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
Logger.info("Federating #{id} to #{inbox}")
host = URI.parse(inbox).host
- signature = Pleroma.Web.HTTPSignatures.sign(actor, %{host: host, "content-length": byte_size(json)})
- @httpoison.post(inbox, json, [{"Content-Type", "application/activity+json"}, {"signature", signature}], hackney: [pool: :default])
+
+ signature =
+ Pleroma.Web.HTTPSignatures.sign(actor, %{host: host, "content-length": byte_size(json)})
+
+ @httpoison.post(
+ inbox,
+ json,
+ [{"Content-Type", "application/activity+json"}, {"signature", signature}],
+ hackney: [pool: :default]
+ )
end
# TODO:
@@ -368,17 +460,34 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, object}
else
Logger.info("Fetching #{id} via AP")
+
with true <- String.starts_with?(id, "http"),
- {:ok, %{body: body, status_code: code}} when code in 200..299 <- @httpoison.get(id, [Accept: "application/activity+json"], follow_redirect: true, timeout: 10000, recv_timeout: 20000),
+ {:ok, %{body: body, status_code: code}} when code in 200..299 <-
+ @httpoison.get(
+ id,
+ [Accept: "application/activity+json"],
+ follow_redirect: true,
+ timeout: 10000,
+ recv_timeout: 20000
+ ),
{:ok, data} <- Jason.decode(body),
nil <- Object.get_by_ap_id(data["id"]),
- params <- %{"type" => "Create", "to" => data["to"], "cc" => data["cc"], "actor" => data["attributedTo"], "object" => data},
+ params <- %{
+ "type" => "Create",
+ "to" => data["to"],
+ "cc" => data["cc"],
+ "actor" => data["attributedTo"],
+ "object" => data
+ },
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
{:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
else
- object = %Object{} -> {:ok, object}
+ object = %Object{} ->
+ {:ok, object}
+
e ->
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
+
case OStatus.fetch_activity_from_url(id) do
{:ok, [activity | _]} -> {:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
e -> e
@@ -388,15 +497,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def is_public?(activity) do
- "https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++ (activity.data["cc"] || []))
+ "https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++
+ (activity.data["cc"] || []))
end
def visible_for_user?(activity, nil) do
is_public?(activity)
end
+
def visible_for_user?(activity, user) do
x = [user.ap_id | user.following]
- y = (activity.data["to"] ++ (activity.data["cc"] || []))
+ y = activity.data["to"] ++ (activity.data["cc"] || [])
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 47a8bf5ab..12f61f5f0 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
require Logger
- action_fallback :errors
+ action_fallback(:errors)
def user(conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
@@ -31,6 +31,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
with %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
{page, _} = Integer.parse(page)
+
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("following.json", %{user: user, page: page}))
@@ -50,6 +51,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
with %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
{page, _} = Integer.parse(page)
+
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("followers.json", %{user: user, page: page}))
@@ -74,7 +76,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
end
- def outbox(conn, %{"nickname" => nickname}) do outbox(conn, %{"nickname" => nickname, "max_id" => nil}) end
+ def outbox(conn, %{"nickname" => nickname}) do
+ outbox(conn, %{"nickname" => nickname, "max_id" => nil})
+ end
# TODO: Ensure that this inbox is a recipient of the message
def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
@@ -84,7 +88,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def inbox(conn, params) do
headers = Enum.into(conn.req_headers, %{})
- if !(String.contains?(headers["signature"] || "", params["actor"])) do
+
+ if !String.contains?(headers["signature"] || "", params["actor"]) do
Logger.info("Signature not from author, relayed message, fetching from source")
ActivityPub.fetch_object_from_id(params["object"]["id"])
else
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 6561b8d76..00b9f74ff 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -25,21 +25,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> fix_tag
end
- def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object) when not is_nil(in_reply_to_id) do
+ def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object)
+ when not is_nil(in_reply_to_id) do
case ActivityPub.fetch_object_from_id(in_reply_to_id) do
{:ok, replied_object} ->
activity = Activity.get_create_activity_by_object_ap_id(replied_object.data["id"])
+
object
|> Map.put("inReplyTo", replied_object.data["id"])
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|> Map.put("inReplyToStatusId", activity.id)
|> Map.put("conversation", replied_object.data["context"] || object["conversation"])
|> Map.put("context", replied_object.data["context"] || object["conversation"])
+
e ->
Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
object
end
end
+
def fix_in_reply_to(object), do: object
def fix_context(object) do
@@ -48,27 +52,32 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def fix_attachments(object) do
- attachments = (object["attachment"] || [])
- |> Enum.map(fn (data) ->
- url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}]
- Map.put(data, "url", url)
- end)
+ attachments =
+ (object["attachment"] || [])
+ |> Enum.map(fn data ->
+ url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}]
+ Map.put(data, "url", url)
+ end)
object
|> Map.put("attachment", attachments)
end
def fix_emoji(object) do
- tags = (object["tag"] || [])
- emoji = tags |> Enum.filter(fn (data) -> data["type"] == "Emoji" and data["icon"] end)
- emoji = emoji |> Enum.reduce(%{}, fn (data, mapping) ->
- name = data["name"]
- if String.starts_with?(name, ":") do
- name = name |> String.slice(1..-2)
- end
+ tags = object["tag"] || []
+ emoji = tags |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
+
+ emoji =
+ emoji
+ |> Enum.reduce(%{}, fn data, mapping ->
+ name = data["name"]
+
+ if String.starts_with?(name, ":") do
+ name = name |> String.slice(1..-2)
+ end
- mapping |> Map.put(name, data["icon"]["url"])
- end)
+ mapping |> Map.put(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)
@@ -78,9 +87,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def fix_tag(object) do
- tags = (object["tag"] || [])
- |> Enum.filter(fn (data) -> data["type"] == "Hashtag" and data["name"] end)
- |> Enum.map(fn (data) -> String.slice(data["name"], 1..-1) end)
+ tags =
+ (object["tag"] || [])
+ |> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
+ |> Enum.map(fn data -> String.slice(data["name"], 1..-1) end)
combined = (object["tag"] || []) ++ tags
@@ -103,13 +113,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
context: object["conversation"],
local: false,
published: data["published"],
- additional: Map.take(data, [
- "cc",
- "id"
- ])
+ additional:
+ Map.take(data, [
+ "cc",
+ "id"
+ ])
}
-
ActivityPub.create(params)
else
%Activity{} = activity -> {:ok, activity}
@@ -117,11 +127,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data) do
+ def handle_incoming(
+ %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data
+ ) do
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
%User{} = follower <- User.get_or_fetch_by_ap_id(follower),
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
ActivityPub.accept(%{to: [follower.ap_id], actor: followed.ap_id, object: data, local: true})
+
User.follow(follower, followed)
{:ok, activity}
else
@@ -129,7 +142,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(%{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = data) do
+ def handle_incoming(
+ %{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = data
+ ) do
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
{:ok, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
{:ok, activity, object} <- ActivityPub.like(actor, object, id, false) do
@@ -139,7 +154,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(%{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = data) do
+ def handle_incoming(
+ %{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = data
+ ) do
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
{:ok, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
{:ok, activity, object} <- ActivityPub.announce(actor, object, id, false) do
@@ -149,20 +166,31 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(%{"type" => "Update", "object" => %{"type" => "Person"} = object, "actor" => actor_id} = data) do
+ def handle_incoming(
+ %{"type" => "Update", "object" => %{"type" => "Person"} = object, "actor" => actor_id} =
+ data
+ ) do
with %User{ap_id: ^actor_id} = actor <- User.get_by_ap_id(object["id"]) do
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
banner = new_user_data[:info]["banner"]
- update_data = new_user_data
- |> Map.take([:name, :bio, :avatar])
- |> Map.put(:info, Map.merge(actor.info, %{"banner" => banner}))
+
+ update_data =
+ new_user_data
+ |> Map.take([:name, :bio, :avatar])
+ |> Map.put(:info, Map.merge(actor.info, %{"banner" => banner}))
actor
|> User.upgrade_changeset(update_data)
|> User.update_and_set_cache()
- ActivityPub.update(%{local: false, to: data["to"] || [], cc: data["cc"] || [], object: object, actor: actor_id})
+ ActivityPub.update(%{
+ local: false,
+ to: data["to"] || [],
+ cc: data["cc"] || [],
+ object: object,
+ actor: actor_id
+ })
else
e ->
Logger.error(e)
@@ -171,11 +199,15 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
# TODO: Make secure.
- def handle_incoming(%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data) do
- object_id = case object_id do
- %{"id" => id} -> id
- id -> id
- end
+ def handle_incoming(
+ %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data
+ ) do
+ object_id =
+ case object_id do
+ %{"id" => id} -> id
+ id -> id
+ end
+
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
{:ok, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
{:ok, activity} <- ActivityPub.delete(object, false) do
@@ -203,6 +235,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
_e -> object
end
end
+
def set_reply_to_uri(obj), do: obj
# Prepares the object of an outgoing create activity.
@@ -222,20 +255,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"""
internal -> Mastodon
"""
+
def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
- object = object
- |> prepare_object
- data = data
- |> Map.put("object", object)
- |> Map.put("@context", "https://www.w3.org/ns/activitystreams")
+ object =
+ object
+ |> prepare_object
+
+ data =
+ data
+ |> Map.put("object", object)
+ |> Map.put("@context", "https://www.w3.org/ns/activitystreams")
{:ok, data}
end
def prepare_outgoing(%{"type" => type} = data) do
- data = data
- |> maybe_fix_object_url
- |> Map.put("@context", "https://www.w3.org/ns/activitystreams")
+ data =
+ data
+ |> maybe_fix_object_url
+ |> Map.put("@context", "https://www.w3.org/ns/activitystreams")
{:ok, data}
end
@@ -245,11 +283,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
case ActivityPub.fetch_object_from_id(data["object"]) do
{:ok, relative_object} ->
if relative_object.data["external_url"] do
- data = data
- |> Map.put("object", relative_object.data["external_url"])
+ data =
+ data
+ |> Map.put("object", relative_object.data["external_url"])
else
data
end
+
e ->
Logger.error("Couldn't fetch #{data["object"]} #{inspect(e)}")
data
@@ -260,8 +300,15 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def add_hashtags(object) do
- tags = (object["tag"] || [])
- |> Enum.map fn (tag) -> %{"href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}", "name" => "##{tag}", "type" => "Hashtag"} end
+ tags =
+ (object["tag"] || [])
+ |> Enum.map(fn tag ->
+ %{
+ "href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}",
+ "name" => "##{tag}",
+ "type" => "Hashtag"
+ }
+ end)
object
|> Map.put("tag", tags)
@@ -269,10 +316,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def add_mention_tags(object) do
recipients = object["to"] ++ (object["cc"] || [])
- mentions = recipients
- |> Enum.map(fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end)
- |> Enum.filter(&(&1))
- |> Enum.map(fn(user) -> %{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"} end)
+
+ mentions =
+ recipients
+ |> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
+ |> Enum.filter(& &1)
+ |> Enum.map(fn user ->
+ %{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"}
+ end)
tags = object["tag"] || []
@@ -284,13 +335,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def add_emoji_tags(object) do
tags = object["tag"] || []
emoji = object["emoji"] || []
- out = emoji |> Enum.map(fn {name, url} ->
- %{"icon" => %{"url" => url, "type" => "Image"},
- "name" => ":" <> name <> ":",
- "type" => "Emoji",
- "updated" => "1970-01-01T00:00:00Z",
- "id" => url}
- end)
+
+ out =
+ emoji
+ |> Enum.map(fn {name, url} ->
+ %{
+ "icon" => %{"url" => url, "type" => "Image"},
+ "name" => ":" <> name <> ":",
+ "type" => "Emoji",
+ "updated" => "1970-01-01T00:00:00Z",
+ "id" => url
+ }
+ end)
object
|> Map.put("tag", tags ++ out)
@@ -313,11 +369,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def prepare_attachments(object) do
- attachments = (object["attachment"] || [])
- |> Enum.map(fn (data) ->
- [%{"mediaType" => media_type, "href" => href} | _] = data["url"]
- %{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"}
- end)
+ attachments =
+ (object["attachment"] || [])
+ |> Enum.map(fn data ->
+ [%{"mediaType" => media_type, "href" => href} | _] = data["url"]
+ %{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"}
+ end)
object
|> Map.put("attachment", attachments)
@@ -325,9 +382,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
defp user_upgrade_task(user) do
old_follower_address = User.ap_followers(user)
- q = from u in User,
- where: ^old_follower_address in u.following,
- update: [set: [following: fragment("array_replace(?,?,?)", u.following, ^old_follower_address, ^user.follower_address)]]
+
+ q =
+ from(
+ u in User,
+ where: ^old_follower_address in u.following,
+ update: [
+ set: [
+ following:
+ fragment(
+ "array_replace(?,?,?)",
+ u.following,
+ ^old_follower_address,
+ ^user.follower_address
+ )
+ ]
+ ]
+ )
+
Repo.update_all(q, [])
maybe_retire_websub(user.ap_id)
@@ -335,22 +407,40 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# 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: fragment("array_replace(?,?,?)", a.recipients, ^old_follower_address, ^user.follower_address)]]
+
+ q =
+ from(
+ a in Activity,
+ where: ^old_follower_address in a.recipients,
+ where: a.id > ^since,
+ update: [
+ set: [
+ recipients:
+ fragment(
+ "array_replace(?,?,?)",
+ a.recipients,
+ ^old_follower_address,
+ ^user.follower_address
+ )
+ ]
+ ]
+ )
+
Repo.update_all(q, [])
end
def upgrade_user_from_ap_id(ap_id, async \\ true) do
with %User{local: false} = user <- User.get_by_ap_id(ap_id),
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id) do
- data = data
- |> Map.put(:info, Map.merge(user.info, data[:info]))
+ data =
+ data
+ |> Map.put(:info, Map.merge(user.info, data[:info]))
already_ap = User.ap_enabled?(user)
- {:ok, user} = User.upgrade_changeset(user, data)
- |> Repo.update()
+
+ {:ok, user} =
+ User.upgrade_changeset(user, data)
+ |> Repo.update()
if !already_ap do
# This could potentially take a long time, do it in the background
@@ -371,9 +461,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def maybe_retire_websub(ap_id) do
# some sanity checks
- if is_binary(ap_id) && (String.length(ap_id) > 8) do
- q = from ws in Pleroma.Web.Websub.WebsubClientSubscription,
- where: fragment("? like ?", ws.topic, ^"#{ap_id}%")
+ if is_binary(ap_id) && String.length(ap_id) > 8 do
+ q =
+ from(
+ ws in Pleroma.Web.Websub.WebsubClientSubscription,
+ where: fragment("? like ?", ws.topic, ^"#{ap_id}%")
+ )
+
Repo.delete_all(q)
end
end
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index a25b27aab..16a1d3e97 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -26,7 +26,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
def make_date do
- DateTime.utc_now() |> DateTime.to_iso8601
+ DateTime.utc_now() |> DateTime.to_iso8601()
end
def generate_activity_id do
@@ -38,25 +38,28 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
def generate_object_id do
- Helpers.o_status_url(Endpoint, :object, UUID.generate)
+ Helpers.o_status_url(Endpoint, :object, UUID.generate())
end
def generate_id(type) do
- "#{Web.base_url()}/#{type}/#{UUID.generate}"
+ "#{Web.base_url()}/#{type}/#{UUID.generate()}"
end
@doc """
Enqueues an activity for federation if it's local
"""
def maybe_federate(%Activity{local: true} = activity) do
- priority = case activity.data["type"] do
- "Delete" -> 10
- "Create" -> 1
- _ -> 5
- end
+ priority =
+ case activity.data["type"] do
+ "Delete" -> 10
+ "Create" -> 1
+ _ -> 5
+ end
+
Pleroma.Web.Federator.enqueue(:publish, activity, priority)
:ok
end
+
def maybe_federate(_), do: :ok
@doc """
@@ -64,9 +67,10 @@ defmodule Pleroma.Web.ActivityPub.Utils do
also adds it to an included object
"""
def lazy_put_activity_defaults(map) do
- map = map
- |> Map.put_new_lazy("id", &generate_activity_id/0)
- |> Map.put_new_lazy("published", &make_date/0)
+ map =
+ map
+ |> Map.put_new_lazy("id", &generate_activity_id/0)
+ |> Map.put_new_lazy("published", &make_date/0)
if is_map(map["object"]) do
object = lazy_put_object_defaults(map["object"])
@@ -88,11 +92,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do
@doc """
Inserts a full object if it is contained in an activity.
"""
- def insert_full_object(%{"object" => %{"type" => type} = object_data}) when is_map(object_data) and type in ["Note"] do
+ def insert_full_object(%{"object" => %{"type" => type} = object_data})
+ when is_map(object_data) and type in ["Note"] do
with {:ok, _} <- Object.create(object_data) do
:ok
end
end
+
def insert_full_object(_), do: :ok
def update_object_in_activities(%{data: %{"id" => id}} = object) do
@@ -101,7 +107,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
# Alternatively, just don't do this and fetch the current object each time. Most
# could probably be taken from cache.
relevant_activities = Activity.all_by_object_ap_id(id)
- Enum.map(relevant_activities, fn (activity) ->
+
+ Enum.map(relevant_activities, fn activity ->
new_activity_data = activity.data |> Map.put("object", object.data)
changeset = Changeset.change(activity, data: new_activity_data)
Repo.update(changeset)
@@ -114,11 +121,20 @@ defmodule Pleroma.Web.ActivityPub.Utils do
Returns an existing like if a user already liked an object
"""
def get_existing_like(actor, %{data: %{"id" => id}}) do
- query = from activity in Activity,
- where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
- # this is to use the index
- where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^id),
- where: fragment("(?)->>'type' = 'Like'", activity.data)
+ query =
+ from(
+ activity in Activity,
+ where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
+ # this is to use the index
+ where:
+ fragment(
+ "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
+ activity.data,
+ activity.data,
+ ^id
+ ),
+ where: fragment("(?)->>'type' = 'Like'", activity.data)
+ )
Repo.one(query)
end
@@ -137,10 +153,12 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
def update_element_in_object(property, element, object) do
- with new_data <- object.data |> Map.put("#{property}_count", length(element)) |> Map.put("#{property}s", element),
+ with new_data <-
+ object.data |> Map.put("#{property}_count", length(element))
+ |> Map.put("#{property}s", element),
changeset <- Changeset.change(object, data: new_data),
{:ok, object} <- Repo.update(changeset),
- _ <- update_object_in_activities(object) do
+ _ <- update_object_in_activities(object) do
{:ok, object}
end
end
@@ -150,7 +168,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do
- with likes <- [actor | (object.data["likes"] || [])] |> Enum.uniq do
+ with likes <- [actor | object.data["likes"] || []] |> Enum.uniq() do
update_likes_in_object(likes, object)
end
end
@@ -178,13 +196,20 @@ defmodule Pleroma.Web.ActivityPub.Utils do
if activity_id, do: Map.put(data, "id", activity_id), else: data
end
- def fetch_latest_follow(%User{ap_id: follower_id},
- %User{ap_id: followed_id}) do
- query = from activity in Activity,
- where: fragment("? @> ?", activity.data, ^%{type: "Follow", actor: follower_id,
- object: followed_id}),
- order_by: [desc: :id],
- limit: 1
+ def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do
+ query =
+ from(
+ activity in Activity,
+ where:
+ fragment(
+ "? @> ?",
+ activity.data,
+ ^%{type: "Follow", actor: follower_id, object: followed_id}
+ ),
+ order_by: [desc: :id],
+ limit: 1
+ )
+
Repo.one(query)
end
@@ -193,7 +218,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do
@doc """
Make announce activity data for the given actor and object
"""
- def make_announce_data(%User{ap_id: ap_id} = user, %Object{data: %{"id" => id}} = object, activity_id) do
+ def make_announce_data(
+ %User{ap_id: ap_id} = user,
+ %Object{data: %{"id" => id}} = object,
+ activity_id
+ ) do
data = %{
"type" => "Announce",
"actor" => ap_id,
@@ -207,7 +236,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
def add_announce_to_object(%Activity{data: %{"actor" => actor}}, object) do
- with announcements <- [actor | (object.data["announcements"] || [])] |> Enum.uniq do
+ with announcements <- [actor | object.data["announcements"] || []] |> Enum.uniq() do
update_element_in_object("announcement", announcements, object)
end
end
@@ -223,14 +252,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do
}
end
-
#### Create-related helpers
def make_create_data(params, additional) do
published = params.published || make_date()
+
%{
"type" => "Create",
- "to" => params.to |> Enum.uniq,
+ "to" => params.to |> Enum.uniq(),
"actor" => params.actor.ap_id,
"object" => params.object,
"published" => published,
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 9ff1f0c77..997d8308f 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
{:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"])
public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
public_key = :public_key.pem_encode([public_key])
+
%{
"id" => user.ap_id,
"type" => "Person",
@@ -30,7 +31,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"publicKeyPem" => public_key
},
"endpoints" => %{
- "sharedInbox" => "#{Pleroma.Web.Endpoint.url}/inbox"
+ "sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox"
},
"icon" => %{
"type" => "Image",
@@ -47,7 +48,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def collection(collection, iri, page) do
offset = (page - 1) * 10
items = Enum.slice(collection, offset, 10)
- items = Enum.map(items, fn (user) -> user.ap_id end)
+ items = Enum.map(items, fn user -> user.ap_id end)
+
map = %{
"id" => "#{iri}?page=#{page}",
"type" => "OrderedCollectionPage",
@@ -55,19 +57,22 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"totalItems" => length(collection),
"orderedItems" => items
}
+
if offset < length(collection) do
- Map.put(map, "next", "#{iri}?page=#{page+1}")
+ Map.put(map, "next", "#{iri}?page=#{page + 1}")
end
end
def render("following.json", %{user: user, page: page}) do
{:ok, following} = User.get_friends(user)
+
collection(following, "#{user.ap_id}/following", page)
|> Map.merge(Utils.make_json_ld_header())
end
def render("following.json", %{user: user}) do
{:ok, following} = User.get_friends(user)
+
%{
"id" => "#{user.ap_id}/following",
"type" => "OrderedCollection",
@@ -79,12 +84,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def render("followers.json", %{user: user, page: page}) do
{:ok, followers} = User.get_followers(user)
+
collection(followers, "#{user.ap_id}/followers", page)
|> Map.merge(Utils.make_json_ld_header())
end
def render("followers.json", %{user: user}) do
{:ok, followers} = User.get_followers(user)
+
%{
"id" => "#{user.ap_id}/followers",
"type" => "OrderedCollection",
@@ -115,19 +122,21 @@ defmodule Pleroma.Web.ActivityPub.UserView do
activities = Enum.reverse(activities)
max_id = Enum.at(activities, 0).id
- collection = Enum.map(activities, fn (act) ->
- {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
- data
- end)
+ collection =
+ Enum.map(activities, fn act ->
+ {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
+ data
+ end)
iri = "#{user.ap_id}/outbox"
+
page = %{
"id" => "#{iri}?max_id=#{max_id}",
"type" => "OrderedCollectionPage",
"partOf" => iri,
"totalItems" => info.note_count,
"orderedItems" => collection,
- "next" => "#{iri}?max_id=#{min_id-1}",
+ "next" => "#{iri}?max_id=#{min_id - 1}"
}
if max_qid == nil do