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 chat_message(actor, recipient, content, opts \\ []) do
188 "id" => Utils.generate_object_id(),
189 "actor" => actor.ap_id,
190 "type" => "ChatMessage",
192 "content" => content,
193 "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
194 "emoji" => Emoji.Formatter.get_emoji_map(content)
197 case opts[:attachment] do
198 %Object{data: attachment_data} ->
201 Map.put(basic, "attachment", attachment_data),
210 def answer(user, object, name) do
214 "actor" => user.ap_id,
215 "attributedTo" => user.ap_id,
216 "cc" => [object.data["actor"]],
219 "inReplyTo" => object.data["id"],
220 "context" => object.data["context"],
221 "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
222 "id" => Utils.generate_object_id()
226 @spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
227 def tombstone(actor, id) do
232 "type" => "Tombstone"
236 @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
237 def like(actor, object) do
238 with {:ok, data, meta} <- object_action(actor, object) do
241 |> Map.put("type", "Like")
247 # Retricted to user updates for now, always public
248 @spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
249 def update(actor, object) do
250 to = [Pleroma.Constants.as_public(), actor.follower_address]
254 "id" => Utils.generate_activity_id(),
256 "actor" => actor.ap_id,
262 @spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
263 def block(blocker, blocked) do
266 "id" => Utils.generate_activity_id(),
268 "actor" => blocker.ap_id,
269 "object" => blocked.ap_id,
270 "to" => [blocked.ap_id]
274 @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
275 def announce(actor, object, options \\ []) do
276 public? = Keyword.get(options, :public, false)
280 actor.ap_id == Relay.ap_id() ->
281 [actor.follower_address]
283 public? and Visibility.is_local_public?(object) ->
284 [actor.follower_address, object.data["actor"], Utils.as_local_public()]
287 [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
290 [actor.follower_address, object.data["actor"]]
295 "id" => Utils.generate_activity_id(),
296 "actor" => actor.ap_id,
297 "object" => object.data["id"],
299 "context" => object.data["context"],
300 "type" => "Announce",
301 "published" => Utils.make_date()
305 @spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()}
306 defp object_action(actor, object) do
307 object_actor = User.get_cached_by_ap_id(object.data["actor"])
309 # Address the actor of the object, and our actor's follower collection if the post is public.
311 if Visibility.is_public?(object) do
312 [actor.follower_address, object.data["actor"]]
314 [object.data["actor"]]
317 # CC everyone who's been addressed in the object, except ourself and the object actor's
318 # follower collection
320 (object.data["to"] ++ (object.data["cc"] || []))
321 |> List.delete(actor.ap_id)
322 |> List.delete(object_actor.follower_address)
326 "id" => Utils.generate_activity_id(),
327 "actor" => actor.ap_id,
328 "object" => object.data["id"],
331 "context" => object.data["context"]
335 @spec pin(User.t(), Object.t()) :: {:ok, map(), keyword()}
336 def pin(%User{} = user, object) do
339 "id" => Utils.generate_activity_id(),
340 "target" => pinned_url(user.nickname),
341 "object" => object.data["id"],
342 "actor" => user.ap_id,
344 "to" => [Pleroma.Constants.as_public()],
345 "cc" => [user.follower_address]
349 @spec unpin(User.t(), Object.t()) :: {:ok, map, keyword()}
350 def unpin(%User{} = user, object) do
353 "id" => Utils.generate_activity_id(),
354 "target" => pinned_url(user.nickname),
355 "object" => object.data["id"],
356 "actor" => user.ap_id,
358 "to" => [Pleroma.Constants.as_public()],
359 "cc" => [user.follower_address]
363 defp pinned_url(nickname) when is_binary(nickname) do
364 Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)