1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 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 @spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
222 def update(actor, object) do
224 if object["type"] in Pleroma.Constants.actor_types() do
225 # User updates, always public
226 {[Pleroma.Constants.as_public(), actor.follower_address], []}
228 # Status updates, follow the recipients in the object
229 {object["to"] || [], object["cc"] || []}
234 "id" => Utils.generate_activity_id(),
236 "actor" => actor.ap_id,
243 @spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
244 def block(blocker, blocked) do
247 "id" => Utils.generate_activity_id(),
249 "actor" => blocker.ap_id,
250 "object" => blocked.ap_id,
251 "to" => [blocked.ap_id]
255 @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
256 def announce(actor, object, options \\ []) do
257 public? = Keyword.get(options, :public, false)
261 actor.ap_id == Relay.ap_id() ->
262 [actor.follower_address]
264 public? and Visibility.is_local_public?(object) ->
265 [actor.follower_address, object.data["actor"], Utils.as_local_public()]
268 [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
271 [actor.follower_address, object.data["actor"]]
276 "id" => Utils.generate_activity_id(),
277 "actor" => actor.ap_id,
278 "object" => object.data["id"],
280 "context" => object.data["context"],
281 "type" => "Announce",
282 "published" => Utils.make_date()
286 @spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()}
287 defp object_action(actor, object) do
288 object_actor = User.get_cached_by_ap_id(object.data["actor"])
290 # Address the actor of the object, and our actor's follower collection if the post is public.
292 if Visibility.is_public?(object) do
293 [actor.follower_address, object.data["actor"]]
295 [object.data["actor"]]
298 # CC everyone who's been addressed in the object, except ourself and the object actor's
299 # follower collection
301 (object.data["to"] ++ (object.data["cc"] || []))
302 |> List.delete(actor.ap_id)
303 |> List.delete(object_actor.follower_address)
307 "id" => Utils.generate_activity_id(),
308 "actor" => actor.ap_id,
309 "object" => object.data["id"],
312 "context" => object.data["context"]
316 @spec pin(User.t(), Object.t()) :: {:ok, map(), keyword()}
317 def pin(%User{} = user, object) do
320 "id" => Utils.generate_activity_id(),
321 "target" => pinned_url(user.nickname),
322 "object" => object.data["id"],
323 "actor" => user.ap_id,
325 "to" => [Pleroma.Constants.as_public()],
326 "cc" => [user.follower_address]
330 @spec unpin(User.t(), Object.t()) :: {:ok, map, keyword()}
331 def unpin(%User{} = user, object) do
334 "id" => Utils.generate_activity_id(),
335 "target" => pinned_url(user.nickname),
336 "object" => object.data["id"],
337 "actor" => user.ap_id,
339 "to" => [Pleroma.Constants.as_public()],
340 "cc" => [user.follower_address]
344 defp pinned_url(nickname) when is_binary(nickname) do
345 Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)