1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.ActivityPub.Builder do
7 This module builds the objects. Meant to be used for creating local objects.
9 This module encodes our addressing policies and general shape of our objects.
15 alias Pleroma.Web.ActivityPub.Relay
16 alias Pleroma.Web.ActivityPub.Utils
17 alias Pleroma.Web.ActivityPub.Visibility
18 alias Pleroma.Web.CommonAPI.ActivityDraft
19 alias Pleroma.Web.Endpoint
21 require Pleroma.Constants
23 def accept_or_reject(actor, activity, type) do
25 "id" => Utils.generate_activity_id(),
26 "actor" => actor.ap_id,
28 "object" => activity.data["id"],
29 "to" => [activity.actor]
35 @spec reject(User.t(), Activity.t()) :: {:ok, map(), keyword()}
36 def reject(actor, rejected_activity) do
37 accept_or_reject(actor, rejected_activity, "Reject")
40 @spec accept(User.t(), Activity.t()) :: {:ok, map(), keyword()}
41 def accept(actor, accepted_activity) do
42 accept_or_reject(actor, accepted_activity, "Accept")
45 @spec follow(User.t(), User.t()) :: {:ok, map(), keyword()}
46 def follow(follower, followed) do
48 "id" => Utils.generate_activity_id(),
49 "actor" => follower.ap_id,
51 "object" => followed.ap_id,
52 "to" => [followed.ap_id]
58 @spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
59 def emoji_react(actor, object, emoji) do
60 with {:ok, data, meta} <- object_action(actor, object) do
62 if Emoji.is_unicode_emoji?(emoji) do
64 |> Map.put("content", emoji)
65 |> Map.put("type", "EmojiReact")
67 with %{} = emojo <- Emoji.get(emoji) do
68 path = emojo |> Map.get(:file)
69 url = "#{Endpoint.url()}#{path}"
72 |> Map.put("content", emoji)
73 |> Map.put("type", "EmojiReact")
77 |> Map.put("type", "Emoji")
78 |> Map.put("name", emojo.code)
82 |> Map.put("type", "Image")
83 |> Map.put("url", url)
87 _ -> {:error, "Emoji does not exist"}
95 @spec undo(User.t(), Activity.t()) :: {:ok, map(), keyword()}
96 def undo(actor, object) do
99 "id" => Utils.generate_activity_id(),
100 "actor" => actor.ap_id,
102 "object" => object.data["id"],
103 "to" => object.data["to"] || [],
104 "cc" => object.data["cc"] || []
108 @spec delete(User.t(), String.t()) :: {:ok, map(), keyword()}
109 def delete(actor, object_id) do
110 object = Object.normalize(object_id, fetch: false)
112 user = !object && User.get_cached_by_ap_id(object_id)
115 case {object, user} do
117 # We are deleting an object, address everyone who was originally mentioned
118 (object.data["to"] || []) ++ (object.data["cc"] || [])
120 {_, %User{follower_address: follower_address}} ->
121 # We are deleting a user, address the followers of that user
127 "id" => Utils.generate_activity_id(),
128 "actor" => actor.ap_id,
129 "object" => object_id,
135 def create(actor, object, recipients) do
145 "id" => Utils.generate_activity_id(),
146 "actor" => actor.ap_id,
150 "published" => DateTime.utc_now() |> DateTime.to_iso8601()
152 |> Pleroma.Maps.put_if_present("context", context), []}
155 @spec note(ActivityDraft.t()) :: {:ok, map(), keyword()}
156 def note(%ActivityDraft{} = draft) do
162 "content" => draft.content_html,
163 "summary" => draft.summary,
164 "sensitive" => draft.sensitive,
165 "context" => draft.context,
166 "attachment" => draft.attachments,
167 "actor" => draft.user.ap_id,
168 "tag" => Keyword.values(draft.tags) |> Enum.uniq()
170 |> add_in_reply_to(draft.in_reply_to)
171 |> add_quote(draft.quote)
172 |> Map.merge(draft.extra)
177 defp add_in_reply_to(object, nil), do: object
179 defp add_in_reply_to(object, in_reply_to) do
180 with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
181 Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
187 defp add_quote(object, nil), do: object
189 defp add_quote(object, quote) do
190 with %Object{} = quote_object <- Object.normalize(quote, fetch: false) do
191 Map.put(object, "quoteUri", quote_object.data["id"])
197 def answer(user, object, name) do
201 "actor" => user.ap_id,
202 "attributedTo" => user.ap_id,
203 "cc" => [object.data["actor"]],
206 "inReplyTo" => object.data["id"],
207 "context" => object.data["context"],
208 "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
209 "id" => Utils.generate_object_id()
213 @spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
214 def tombstone(actor, id) do
219 "type" => "Tombstone"
223 @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
224 def like(actor, object) do
225 with {:ok, data, meta} <- object_action(actor, object) do
228 |> Map.put("type", "Like")
234 # Retricted to user updates for now, always public
235 @spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
236 def update(actor, object) do
237 to = [Pleroma.Constants.as_public(), actor.follower_address]
241 "id" => Utils.generate_activity_id(),
243 "actor" => actor.ap_id,
249 @spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
250 def block(blocker, blocked) do
253 "id" => Utils.generate_activity_id(),
255 "actor" => blocker.ap_id,
256 "object" => blocked.ap_id,
257 "to" => [blocked.ap_id]
261 @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
262 def announce(actor, object, options \\ []) do
263 public? = Keyword.get(options, :public, false)
267 actor.ap_id == Relay.ap_id() ->
268 [actor.follower_address]
270 public? and Visibility.is_local_public?(object) ->
271 [actor.follower_address, object.data["actor"], Utils.as_local_public()]
274 [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
277 [actor.follower_address, object.data["actor"]]
282 "id" => Utils.generate_activity_id(),
283 "actor" => actor.ap_id,
284 "object" => object.data["id"],
286 "context" => object.data["context"],
287 "type" => "Announce",
288 "published" => Utils.make_date()
292 @spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()}
293 defp object_action(actor, object) do
294 object_actor = User.get_cached_by_ap_id(object.data["actor"])
296 # Address the actor of the object, and our actor's follower collection if the post is public.
298 if Visibility.is_public?(object) do
299 [actor.follower_address, object.data["actor"]]
301 [object.data["actor"]]
304 # CC everyone who's been addressed in the object, except ourself and the object actor's
305 # follower collection
307 (object.data["to"] ++ (object.data["cc"] || []))
308 |> List.delete(actor.ap_id)
309 |> List.delete(object_actor.follower_address)
313 "id" => Utils.generate_activity_id(),
314 "actor" => actor.ap_id,
315 "object" => object.data["id"],
318 "context" => object.data["context"]
322 @spec pin(User.t(), Object.t()) :: {:ok, map(), keyword()}
323 def pin(%User{} = user, object) do
326 "id" => Utils.generate_activity_id(),
327 "target" => pinned_url(user.nickname),
328 "object" => object.data["id"],
329 "actor" => user.ap_id,
331 "to" => [Pleroma.Constants.as_public()],
332 "cc" => [user.follower_address]
336 @spec unpin(User.t(), Object.t()) :: {:ok, map, keyword()}
337 def unpin(%User{} = user, object) do
340 "id" => Utils.generate_activity_id(),
341 "target" => pinned_url(user.nickname),
342 "object" => object.data["id"],
343 "actor" => user.ap_id,
345 "to" => [Pleroma.Constants.as_public()],
346 "cc" => [user.follower_address]
350 defp pinned_url(nickname) when is_binary(nickname) do
351 Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)