Merge branch 'cycles-base-url' into 'develop'
[akkoma] / lib / pleroma / web / activity_pub / builder.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.Builder do
6 @moduledoc """
7 This module builds the objects. Meant to be used for creating local objects.
8
9 This module encodes our addressing policies and general shape of our objects.
10 """
11
12 alias Pleroma.Emoji
13 alias Pleroma.Object
14 alias Pleroma.User
15 alias Pleroma.Web.ActivityPub.Relay
16 alias Pleroma.Web.ActivityPub.Utils
17 alias Pleroma.Web.ActivityPub.Visibility
18
19 require Pleroma.Constants
20
21 def accept_or_reject(actor, activity, type) do
22 data = %{
23 "id" => Utils.generate_activity_id(),
24 "actor" => actor.ap_id,
25 "type" => type,
26 "object" => activity.data["id"],
27 "to" => [activity.actor]
28 }
29
30 {:ok, data, []}
31 end
32
33 @spec reject(User.t(), Activity.t()) :: {:ok, map(), keyword()}
34 def reject(actor, rejected_activity) do
35 accept_or_reject(actor, rejected_activity, "Reject")
36 end
37
38 @spec accept(User.t(), Activity.t()) :: {:ok, map(), keyword()}
39 def accept(actor, accepted_activity) do
40 accept_or_reject(actor, accepted_activity, "Accept")
41 end
42
43 @spec follow(User.t(), User.t()) :: {:ok, map(), keyword()}
44 def follow(follower, followed) do
45 data = %{
46 "id" => Utils.generate_activity_id(),
47 "actor" => follower.ap_id,
48 "type" => "Follow",
49 "object" => followed.ap_id,
50 "to" => [followed.ap_id]
51 }
52
53 {:ok, data, []}
54 end
55
56 @spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
57 def emoji_react(actor, object, emoji) do
58 with {:ok, data, meta} <- object_action(actor, object) do
59 data =
60 data
61 |> Map.put("content", emoji)
62 |> Map.put("type", "EmojiReact")
63
64 {:ok, data, meta}
65 end
66 end
67
68 @spec undo(User.t(), Activity.t()) :: {:ok, map(), keyword()}
69 def undo(actor, object) do
70 {:ok,
71 %{
72 "id" => Utils.generate_activity_id(),
73 "actor" => actor.ap_id,
74 "type" => "Undo",
75 "object" => object.data["id"],
76 "to" => object.data["to"] || [],
77 "cc" => object.data["cc"] || []
78 }, []}
79 end
80
81 @spec delete(User.t(), String.t()) :: {:ok, map(), keyword()}
82 def delete(actor, object_id) do
83 object = Object.normalize(object_id, fetch: false)
84
85 user = !object && User.get_cached_by_ap_id(object_id)
86
87 to =
88 case {object, user} do
89 {%Object{}, _} ->
90 # We are deleting an object, address everyone who was originally mentioned
91 (object.data["to"] || []) ++ (object.data["cc"] || [])
92
93 {_, %User{follower_address: follower_address}} ->
94 # We are deleting a user, address the followers of that user
95 [follower_address]
96 end
97
98 {:ok,
99 %{
100 "id" => Utils.generate_activity_id(),
101 "actor" => actor.ap_id,
102 "object" => object_id,
103 "to" => to,
104 "type" => "Delete"
105 }, []}
106 end
107
108 def create(actor, object, recipients) do
109 context =
110 if is_map(object) do
111 object["context"]
112 else
113 nil
114 end
115
116 {:ok,
117 %{
118 "id" => Utils.generate_activity_id(),
119 "actor" => actor.ap_id,
120 "to" => recipients,
121 "object" => object,
122 "type" => "Create",
123 "published" => DateTime.utc_now() |> DateTime.to_iso8601()
124 }
125 |> Pleroma.Maps.put_if_present("context", context), []}
126 end
127
128 def chat_message(actor, recipient, content, opts \\ []) do
129 basic = %{
130 "id" => Utils.generate_object_id(),
131 "actor" => actor.ap_id,
132 "type" => "ChatMessage",
133 "to" => [recipient],
134 "content" => content,
135 "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
136 "emoji" => Emoji.Formatter.get_emoji_map(content)
137 }
138
139 case opts[:attachment] do
140 %Object{data: attachment_data} ->
141 {
142 :ok,
143 Map.put(basic, "attachment", attachment_data),
144 []
145 }
146
147 _ ->
148 {:ok, basic, []}
149 end
150 end
151
152 def answer(user, object, name) do
153 {:ok,
154 %{
155 "type" => "Answer",
156 "actor" => user.ap_id,
157 "attributedTo" => user.ap_id,
158 "cc" => [object.data["actor"]],
159 "to" => [],
160 "name" => name,
161 "inReplyTo" => object.data["id"],
162 "context" => object.data["context"],
163 "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
164 "id" => Utils.generate_object_id()
165 }, []}
166 end
167
168 @spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
169 def tombstone(actor, id) do
170 {:ok,
171 %{
172 "id" => id,
173 "actor" => actor,
174 "type" => "Tombstone"
175 }, []}
176 end
177
178 @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
179 def like(actor, object) do
180 with {:ok, data, meta} <- object_action(actor, object) do
181 data =
182 data
183 |> Map.put("type", "Like")
184
185 {:ok, data, meta}
186 end
187 end
188
189 # Retricted to user updates for now, always public
190 @spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
191 def update(actor, object) do
192 to = [Pleroma.Constants.as_public(), actor.follower_address]
193
194 {:ok,
195 %{
196 "id" => Utils.generate_activity_id(),
197 "type" => "Update",
198 "actor" => actor.ap_id,
199 "object" => object,
200 "to" => to
201 }, []}
202 end
203
204 @spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
205 def block(blocker, blocked) do
206 {:ok,
207 %{
208 "id" => Utils.generate_activity_id(),
209 "type" => "Block",
210 "actor" => blocker.ap_id,
211 "object" => blocked.ap_id,
212 "to" => [blocked.ap_id]
213 }, []}
214 end
215
216 @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
217 def announce(actor, object, options \\ []) do
218 public? = Keyword.get(options, :public, false)
219
220 to =
221 cond do
222 actor.ap_id == Relay.ap_id() ->
223 [actor.follower_address]
224
225 public? and Visibility.is_local_public?(object) ->
226 [actor.follower_address, object.data["actor"], Pleroma.Constants.as_local_public()]
227
228 public? ->
229 [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
230
231 true ->
232 [actor.follower_address, object.data["actor"]]
233 end
234
235 {:ok,
236 %{
237 "id" => Utils.generate_activity_id(),
238 "actor" => actor.ap_id,
239 "object" => object.data["id"],
240 "to" => to,
241 "context" => object.data["context"],
242 "type" => "Announce",
243 "published" => Utils.make_date()
244 }, []}
245 end
246
247 @spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()}
248 defp object_action(actor, object) do
249 object_actor = User.get_cached_by_ap_id(object.data["actor"])
250
251 # Address the actor of the object, and our actor's follower collection if the post is public.
252 to =
253 if Visibility.is_public?(object) do
254 [actor.follower_address, object.data["actor"]]
255 else
256 [object.data["actor"]]
257 end
258
259 # CC everyone who's been addressed in the object, except ourself and the object actor's
260 # follower collection
261 cc =
262 (object.data["to"] ++ (object.data["cc"] || []))
263 |> List.delete(actor.ap_id)
264 |> List.delete(object_actor.follower_address)
265
266 {:ok,
267 %{
268 "id" => Utils.generate_activity_id(),
269 "actor" => actor.ap_id,
270 "object" => object.data["id"],
271 "to" => to,
272 "cc" => cc,
273 "context" => object.data["context"]
274 }, []}
275 end
276
277 @spec pin(User.t(), Object.t()) :: {:ok, map(), keyword()}
278 def pin(%User{} = user, object) do
279 {:ok,
280 %{
281 "id" => Utils.generate_activity_id(),
282 "target" => pinned_url(user.nickname),
283 "object" => object.data["id"],
284 "actor" => user.ap_id,
285 "type" => "Add",
286 "to" => [Pleroma.Constants.as_public()],
287 "cc" => [user.follower_address]
288 }, []}
289 end
290
291 @spec unpin(User.t(), Object.t()) :: {:ok, map, keyword()}
292 def unpin(%User{} = user, object) do
293 {:ok,
294 %{
295 "id" => Utils.generate_activity_id(),
296 "target" => pinned_url(user.nickname),
297 "object" => object.data["id"],
298 "actor" => user.ap_id,
299 "type" => "Remove",
300 "to" => [Pleroma.Constants.as_public()],
301 "cc" => [user.follower_address]
302 }, []}
303 end
304
305 defp pinned_url(nickname) when is_binary(nickname) do
306 Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)
307 end
308 end