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
61 data = if Emoji.is_unicode_emoji?(emoji) do
63 |> Map.put("content", emoji)
64 |> Map.put("type", "EmojiReact")
66 emojo = Emoji.get(emoji)
67 path = emojo |> Map.get(:file)
68 url = "#{Endpoint.url()}#{path}"
70 |> Map.put("content", emoji)
71 |> Map.put("type", "EmojiReact")
75 |> Map.put("type", "Emoji")
76 |> Map.put("name", emojo.code)
79 |> Map.put("type", "Image")
80 |> Map.put("url", url)
89 @spec undo(User.t(), Activity.t()) :: {:ok, map(), keyword()}
90 def undo(actor, object) do
93 "id" => Utils.generate_activity_id(),
94 "actor" => actor.ap_id,
96 "object" => object.data["id"],
97 "to" => object.data["to"] || [],
98 "cc" => object.data["cc"] || []
102 @spec delete(User.t(), String.t()) :: {:ok, map(), keyword()}
103 def delete(actor, object_id) do
104 object = Object.normalize(object_id, fetch: false)
106 user = !object && User.get_cached_by_ap_id(object_id)
109 case {object, user} do
111 # We are deleting an object, address everyone who was originally mentioned
112 (object.data["to"] || []) ++ (object.data["cc"] || [])
114 {_, %User{follower_address: follower_address}} ->
115 # We are deleting a user, address the followers of that user
121 "id" => Utils.generate_activity_id(),
122 "actor" => actor.ap_id,
123 "object" => object_id,
129 def create(actor, object, recipients) do
139 "id" => Utils.generate_activity_id(),
140 "actor" => actor.ap_id,
144 "published" => DateTime.utc_now() |> DateTime.to_iso8601()
146 |> Pleroma.Maps.put_if_present("context", context), []}
149 @spec note(ActivityDraft.t()) :: {:ok, map(), keyword()}
150 def note(%ActivityDraft{} = draft) do
156 "content" => draft.content_html,
157 "summary" => draft.summary,
158 "sensitive" => draft.sensitive,
159 "context" => draft.context,
160 "attachment" => draft.attachments,
161 "actor" => draft.user.ap_id,
162 "tag" => Keyword.values(draft.tags) |> Enum.uniq()
164 |> add_in_reply_to(draft.in_reply_to)
165 |> Map.merge(draft.extra)
170 defp add_in_reply_to(object, nil), do: object
172 defp add_in_reply_to(object, in_reply_to) do
173 with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
174 Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
180 def chat_message(actor, recipient, content, opts \\ []) do
182 "id" => Utils.generate_object_id(),
183 "actor" => actor.ap_id,
184 "type" => "ChatMessage",
186 "content" => content,
187 "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
188 "emoji" => Emoji.Formatter.get_emoji_map(content)
191 case opts[:attachment] do
192 %Object{data: attachment_data} ->
195 Map.put(basic, "attachment", attachment_data),
204 def answer(user, object, name) do
208 "actor" => user.ap_id,
209 "attributedTo" => user.ap_id,
210 "cc" => [object.data["actor"]],
213 "inReplyTo" => object.data["id"],
214 "context" => object.data["context"],
215 "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
216 "id" => Utils.generate_object_id()
220 @spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
221 def tombstone(actor, id) do
226 "type" => "Tombstone"
230 @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
231 def like(actor, object) do
232 with {:ok, data, meta} <- object_action(actor, object) do
235 |> Map.put("type", "Like")
241 # Retricted to user updates for now, always public
242 @spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
243 def update(actor, object) do
244 to = [Pleroma.Constants.as_public(), actor.follower_address]
248 "id" => Utils.generate_activity_id(),
250 "actor" => actor.ap_id,
256 @spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
257 def block(blocker, blocked) do
260 "id" => Utils.generate_activity_id(),
262 "actor" => blocker.ap_id,
263 "object" => blocked.ap_id,
264 "to" => [blocked.ap_id]
268 @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
269 def announce(actor, object, options \\ []) do
270 public? = Keyword.get(options, :public, false)
274 actor.ap_id == Relay.ap_id() ->
275 [actor.follower_address]
277 public? and Visibility.is_local_public?(object) ->
278 [actor.follower_address, object.data["actor"], Utils.as_local_public()]
281 [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
284 [actor.follower_address, object.data["actor"]]
289 "id" => Utils.generate_activity_id(),
290 "actor" => actor.ap_id,
291 "object" => object.data["id"],
293 "context" => object.data["context"],
294 "type" => "Announce",
295 "published" => Utils.make_date()
299 @spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()}
300 defp object_action(actor, object) do
301 object_actor = User.get_cached_by_ap_id(object.data["actor"])
303 # Address the actor of the object, and our actor's follower collection if the post is public.
305 if Visibility.is_public?(object) do
306 [actor.follower_address, object.data["actor"]]
308 [object.data["actor"]]
311 # CC everyone who's been addressed in the object, except ourself and the object actor's
312 # follower collection
314 (object.data["to"] ++ (object.data["cc"] || []))
315 |> List.delete(actor.ap_id)
316 |> List.delete(object_actor.follower_address)
320 "id" => Utils.generate_activity_id(),
321 "actor" => actor.ap_id,
322 "object" => object.data["id"],
325 "context" => object.data["context"]
329 @spec pin(User.t(), Object.t()) :: {:ok, map(), keyword()}
330 def pin(%User{} = user, object) do
333 "id" => Utils.generate_activity_id(),
334 "target" => pinned_url(user.nickname),
335 "object" => object.data["id"],
336 "actor" => user.ap_id,
338 "to" => [Pleroma.Constants.as_public()],
339 "cc" => [user.follower_address]
343 @spec unpin(User.t(), Object.t()) :: {:ok, map, keyword()}
344 def unpin(%User{} = user, object) do
347 "id" => Utils.generate_activity_id(),
348 "target" => pinned_url(user.nickname),
349 "object" => object.data["id"],
350 "actor" => user.ap_id,
352 "to" => [Pleroma.Constants.as_public()],
353 "cc" => [user.follower_address]
357 defp pinned_url(nickname) when is_binary(nickname) do
358 Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)