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 |> Map.merge(draft.extra)
176 defp add_in_reply_to(object, nil), do: object
178 defp add_in_reply_to(object, in_reply_to) do
179 with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
180 Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
186 def answer(user, object, name) do
190 "actor" => user.ap_id,
191 "attributedTo" => user.ap_id,
192 "cc" => [object.data["actor"]],
195 "inReplyTo" => object.data["id"],
196 "context" => object.data["context"],
197 "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
198 "id" => Utils.generate_object_id()
202 @spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
203 def tombstone(actor, id) do
208 "type" => "Tombstone"
212 @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
213 def like(actor, object) do
214 with {:ok, data, meta} <- object_action(actor, object) do
217 |> Map.put("type", "Like")
223 # Retricted to user updates for now, always public
224 @spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
225 def update(actor, object) do
226 to = [Pleroma.Constants.as_public(), actor.follower_address]
230 "id" => Utils.generate_activity_id(),
232 "actor" => actor.ap_id,
238 @spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
239 def block(blocker, blocked) do
242 "id" => Utils.generate_activity_id(),
244 "actor" => blocker.ap_id,
245 "object" => blocked.ap_id,
246 "to" => [blocked.ap_id]
250 @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
251 def announce(actor, object, options \\ []) do
252 public? = Keyword.get(options, :public, false)
256 actor.ap_id == Relay.ap_id() ->
257 [actor.follower_address]
259 public? and Visibility.is_local_public?(object) ->
260 [actor.follower_address, object.data["actor"], Utils.as_local_public()]
263 [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
266 [actor.follower_address, object.data["actor"]]
271 "id" => Utils.generate_activity_id(),
272 "actor" => actor.ap_id,
273 "object" => object.data["id"],
275 "context" => object.data["context"],
276 "type" => "Announce",
277 "published" => Utils.make_date()
281 @spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()}
282 defp object_action(actor, object) do
283 object_actor = User.get_cached_by_ap_id(object.data["actor"])
285 # Address the actor of the object, and our actor's follower collection if the post is public.
287 if Visibility.is_public?(object) do
288 [actor.follower_address, object.data["actor"]]
290 [object.data["actor"]]
293 # CC everyone who's been addressed in the object, except ourself and the object actor's
294 # follower collection
296 (object.data["to"] ++ (object.data["cc"] || []))
297 |> List.delete(actor.ap_id)
298 |> List.delete(object_actor.follower_address)
302 "id" => Utils.generate_activity_id(),
303 "actor" => actor.ap_id,
304 "object" => object.data["id"],
307 "context" => object.data["context"]
311 @spec pin(User.t(), Object.t()) :: {:ok, map(), keyword()}
312 def pin(%User{} = user, object) do
315 "id" => Utils.generate_activity_id(),
316 "target" => pinned_url(user.nickname),
317 "object" => object.data["id"],
318 "actor" => user.ap_id,
320 "to" => [Pleroma.Constants.as_public()],
321 "cc" => [user.follower_address]
325 @spec unpin(User.t(), Object.t()) :: {:ok, map, keyword()}
326 def unpin(%User{} = user, object) do
329 "id" => Utils.generate_activity_id(),
330 "target" => pinned_url(user.nickname),
331 "object" => object.data["id"],
332 "actor" => user.ap_id,
334 "to" => [Pleroma.Constants.as_public()],
335 "cc" => [user.follower_address]
339 defp pinned_url(nickname) when is_binary(nickname) do
340 Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)