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 emojo = Emoji.get(emoji)
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)
92 @spec undo(User.t(), Activity.t()) :: {:ok, map(), keyword()}
93 def undo(actor, object) do
96 "id" => Utils.generate_activity_id(),
97 "actor" => actor.ap_id,
99 "object" => object.data["id"],
100 "to" => object.data["to"] || [],
101 "cc" => object.data["cc"] || []
105 @spec delete(User.t(), String.t()) :: {:ok, map(), keyword()}
106 def delete(actor, object_id) do
107 object = Object.normalize(object_id, fetch: false)
109 user = !object && User.get_cached_by_ap_id(object_id)
112 case {object, user} do
114 # We are deleting an object, address everyone who was originally mentioned
115 (object.data["to"] || []) ++ (object.data["cc"] || [])
117 {_, %User{follower_address: follower_address}} ->
118 # We are deleting a user, address the followers of that user
124 "id" => Utils.generate_activity_id(),
125 "actor" => actor.ap_id,
126 "object" => object_id,
132 def create(actor, object, recipients) do
142 "id" => Utils.generate_activity_id(),
143 "actor" => actor.ap_id,
147 "published" => DateTime.utc_now() |> DateTime.to_iso8601()
149 |> Pleroma.Maps.put_if_present("context", context), []}
152 @spec note(ActivityDraft.t()) :: {:ok, map(), keyword()}
153 def note(%ActivityDraft{} = draft) do
159 "content" => draft.content_html,
160 "summary" => draft.summary,
161 "sensitive" => draft.sensitive,
162 "context" => draft.context,
163 "attachment" => draft.attachments,
164 "actor" => draft.user.ap_id,
165 "tag" => Keyword.values(draft.tags) |> Enum.uniq()
167 |> add_in_reply_to(draft.in_reply_to)
168 |> Map.merge(draft.extra)
173 defp add_in_reply_to(object, nil), do: object
175 defp add_in_reply_to(object, in_reply_to) do
176 with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
177 Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
183 def chat_message(actor, recipient, content, opts \\ []) do
185 "id" => Utils.generate_object_id(),
186 "actor" => actor.ap_id,
187 "type" => "ChatMessage",
189 "content" => content,
190 "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
191 "emoji" => Emoji.Formatter.get_emoji_map(content)
194 case opts[:attachment] do
195 %Object{data: attachment_data} ->
198 Map.put(basic, "attachment", attachment_data),
207 def answer(user, object, name) do
211 "actor" => user.ap_id,
212 "attributedTo" => user.ap_id,
213 "cc" => [object.data["actor"]],
216 "inReplyTo" => object.data["id"],
217 "context" => object.data["context"],
218 "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
219 "id" => Utils.generate_object_id()
223 @spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
224 def tombstone(actor, id) do
229 "type" => "Tombstone"
233 @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
234 def like(actor, object) do
235 with {:ok, data, meta} <- object_action(actor, object) do
238 |> Map.put("type", "Like")
244 # Retricted to user updates for now, always public
245 @spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
246 def update(actor, object) do
247 to = [Pleroma.Constants.as_public(), actor.follower_address]
251 "id" => Utils.generate_activity_id(),
253 "actor" => actor.ap_id,
259 @spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
260 def block(blocker, blocked) do
263 "id" => Utils.generate_activity_id(),
265 "actor" => blocker.ap_id,
266 "object" => blocked.ap_id,
267 "to" => [blocked.ap_id]
271 @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
272 def announce(actor, object, options \\ []) do
273 public? = Keyword.get(options, :public, false)
277 actor.ap_id == Relay.ap_id() ->
278 [actor.follower_address]
280 public? and Visibility.is_local_public?(object) ->
281 [actor.follower_address, object.data["actor"], Utils.as_local_public()]
284 [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
287 [actor.follower_address, object.data["actor"]]
292 "id" => Utils.generate_activity_id(),
293 "actor" => actor.ap_id,
294 "object" => object.data["id"],
296 "context" => object.data["context"],
297 "type" => "Announce",
298 "published" => Utils.make_date()
302 @spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()}
303 defp object_action(actor, object) do
304 object_actor = User.get_cached_by_ap_id(object.data["actor"])
306 # Address the actor of the object, and our actor's follower collection if the post is public.
308 if Visibility.is_public?(object) do
309 [actor.follower_address, object.data["actor"]]
311 [object.data["actor"]]
314 # CC everyone who's been addressed in the object, except ourself and the object actor's
315 # follower collection
317 (object.data["to"] ++ (object.data["cc"] || []))
318 |> List.delete(actor.ap_id)
319 |> List.delete(object_actor.follower_address)
323 "id" => Utils.generate_activity_id(),
324 "actor" => actor.ap_id,
325 "object" => object.data["id"],
328 "context" => object.data["context"]
332 @spec pin(User.t(), Object.t()) :: {:ok, map(), keyword()}
333 def pin(%User{} = user, object) do
336 "id" => Utils.generate_activity_id(),
337 "target" => pinned_url(user.nickname),
338 "object" => object.data["id"],
339 "actor" => user.ap_id,
341 "to" => [Pleroma.Constants.as_public()],
342 "cc" => [user.follower_address]
346 @spec unpin(User.t(), Object.t()) :: {:ok, map, keyword()}
347 def unpin(%User{} = user, object) do
350 "id" => Utils.generate_activity_id(),
351 "target" => pinned_url(user.nickname),
352 "object" => object.data["id"],
353 "actor" => user.ap_id,
355 "to" => [Pleroma.Constants.as_public()],
356 "cc" => [user.follower_address]
360 defp pinned_url(nickname) when is_binary(nickname) do
361 Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)