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
20 require Pleroma.Constants
22 def accept_or_reject(actor, activity, type) do
24 "id" => Utils.generate_activity_id(),
25 "actor" => actor.ap_id,
27 "object" => activity.data["id"],
28 "to" => [activity.actor]
34 @spec reject(User.t(), Activity.t()) :: {:ok, map(), keyword()}
35 def reject(actor, rejected_activity) do
36 accept_or_reject(actor, rejected_activity, "Reject")
39 @spec accept(User.t(), Activity.t()) :: {:ok, map(), keyword()}
40 def accept(actor, accepted_activity) do
41 accept_or_reject(actor, accepted_activity, "Accept")
44 @spec follow(User.t(), User.t()) :: {:ok, map(), keyword()}
45 def follow(follower, followed) do
47 "id" => Utils.generate_activity_id(),
48 "actor" => follower.ap_id,
50 "object" => followed.ap_id,
51 "to" => [followed.ap_id]
57 @spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
58 def emoji_react(actor, object, emoji) do
59 with {:ok, data, meta} <- object_action(actor, object) do
62 |> Map.put("content", emoji)
63 |> Map.put("type", "EmojiReact")
69 @spec undo(User.t(), Activity.t()) :: {:ok, map(), keyword()}
70 def undo(actor, object) do
73 "id" => Utils.generate_activity_id(),
74 "actor" => actor.ap_id,
76 "object" => object.data["id"],
77 "to" => object.data["to"] || [],
78 "cc" => object.data["cc"] || []
82 @spec delete(User.t(), String.t()) :: {:ok, map(), keyword()}
83 def delete(actor, object_id) do
84 object = Object.normalize(object_id, fetch: false)
86 user = !object && User.get_cached_by_ap_id(object_id)
89 case {object, user} do
91 # We are deleting an object, address everyone who was originally mentioned
92 (object.data["to"] || []) ++ (object.data["cc"] || [])
94 {_, %User{follower_address: follower_address}} ->
95 # We are deleting a user, address the followers of that user
101 "id" => Utils.generate_activity_id(),
102 "actor" => actor.ap_id,
103 "object" => object_id,
109 def create(actor, object, recipients) do
119 "id" => Utils.generate_activity_id(),
120 "actor" => actor.ap_id,
124 "published" => DateTime.utc_now() |> DateTime.to_iso8601()
126 |> Pleroma.Maps.put_if_present("context", context), []}
129 @spec note(ActivityDraft.t()) :: {:ok, map(), keyword()}
130 def note(%ActivityDraft{} = draft) do
136 "content" => draft.content_html,
137 "summary" => draft.summary,
138 "sensitive" => draft.sensitive,
139 "context" => draft.context,
140 "attachment" => draft.attachments,
141 "actor" => draft.user.ap_id,
142 "tag" => Keyword.values(draft.tags) |> Enum.uniq()
144 |> add_in_reply_to(draft.in_reply_to)
145 |> Map.merge(draft.extra)
150 defp add_in_reply_to(object, nil), do: object
152 defp add_in_reply_to(object, in_reply_to) do
153 with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
154 Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
160 def chat_message(actor, recipient, content, opts \\ []) do
162 "id" => Utils.generate_object_id(),
163 "actor" => actor.ap_id,
164 "type" => "ChatMessage",
166 "content" => content,
167 "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
168 "emoji" => Emoji.Formatter.get_emoji_map(content)
171 case opts[:attachment] do
172 %Object{data: attachment_data} ->
175 Map.put(basic, "attachment", attachment_data),
184 def answer(user, object, name) do
188 "actor" => user.ap_id,
189 "attributedTo" => user.ap_id,
190 "cc" => [object.data["actor"]],
193 "inReplyTo" => object.data["id"],
194 "context" => object.data["context"],
195 "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
196 "id" => Utils.generate_object_id()
200 @spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
201 def tombstone(actor, id) do
206 "type" => "Tombstone"
210 @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
211 def like(actor, object) do
212 with {:ok, data, meta} <- object_action(actor, object) do
215 |> Map.put("type", "Like")
221 # Retricted to user updates for now, always public
222 @spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
223 def update(actor, object) do
224 to = [Pleroma.Constants.as_public(), actor.follower_address]
228 "id" => Utils.generate_activity_id(),
230 "actor" => actor.ap_id,
236 @spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
237 def block(blocker, blocked) do
240 "id" => Utils.generate_activity_id(),
242 "actor" => blocker.ap_id,
243 "object" => blocked.ap_id,
244 "to" => [blocked.ap_id]
248 @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
249 def announce(actor, object, options \\ []) do
250 public? = Keyword.get(options, :public, false)
254 actor.ap_id == Relay.ap_id() ->
255 [actor.follower_address]
257 public? and Visibility.is_local_public?(object) ->
258 [actor.follower_address, object.data["actor"], Utils.as_local_public()]
261 [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
264 [actor.follower_address, object.data["actor"]]
269 "id" => Utils.generate_activity_id(),
270 "actor" => actor.ap_id,
271 "object" => object.data["id"],
273 "context" => object.data["context"],
274 "type" => "Announce",
275 "published" => Utils.make_date()
279 @spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()}
280 defp object_action(actor, object) do
281 object_actor = User.get_cached_by_ap_id(object.data["actor"])
283 # Address the actor of the object, and our actor's follower collection if the post is public.
285 if Visibility.is_public?(object) do
286 [actor.follower_address, object.data["actor"]]
288 [object.data["actor"]]
291 # CC everyone who's been addressed in the object, except ourself and the object actor's
292 # follower collection
294 (object.data["to"] ++ (object.data["cc"] || []))
295 |> List.delete(actor.ap_id)
296 |> List.delete(object_actor.follower_address)
300 "id" => Utils.generate_activity_id(),
301 "actor" => actor.ap_id,
302 "object" => object.data["id"],
305 "context" => object.data["context"]
309 @spec pin(User.t(), Object.t()) :: {:ok, map(), keyword()}
310 def pin(%User{} = user, object) do
313 "id" => Utils.generate_activity_id(),
314 "target" => pinned_url(user.nickname),
315 "object" => object.data["id"],
316 "actor" => user.ap_id,
318 "to" => [Pleroma.Constants.as_public()],
319 "cc" => [user.follower_address]
323 @spec unpin(User.t(), Object.t()) :: {:ok, map, keyword()}
324 def unpin(%User{} = user, object) do
327 "id" => Utils.generate_activity_id(),
328 "target" => pinned_url(user.nickname),
329 "object" => object.data["id"],
330 "actor" => user.ap_id,
332 "to" => [Pleroma.Constants.as_public()],
333 "cc" => [user.follower_address]
337 defp pinned_url(nickname) when is_binary(nickname) do
338 Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)