Merge branch 'issue/2069' into 'develop'
[akkoma] / lib / pleroma / web / activity_pub / builder.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 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, 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? ->
226 [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
227
228 true ->
229 [actor.follower_address, object.data["actor"]]
230 end
231
232 {:ok,
233 %{
234 "id" => Utils.generate_activity_id(),
235 "actor" => actor.ap_id,
236 "object" => object.data["id"],
237 "to" => to,
238 "context" => object.data["context"],
239 "type" => "Announce",
240 "published" => Utils.make_date()
241 }, []}
242 end
243
244 @spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()}
245 defp object_action(actor, object) do
246 object_actor = User.get_cached_by_ap_id(object.data["actor"])
247
248 # Address the actor of the object, and our actor's follower collection if the post is public.
249 to =
250 if Visibility.is_public?(object) do
251 [actor.follower_address, object.data["actor"]]
252 else
253 [object.data["actor"]]
254 end
255
256 # CC everyone who's been addressed in the object, except ourself and the object actor's
257 # follower collection
258 cc =
259 (object.data["to"] ++ (object.data["cc"] || []))
260 |> List.delete(actor.ap_id)
261 |> List.delete(object_actor.follower_address)
262
263 {:ok,
264 %{
265 "id" => Utils.generate_activity_id(),
266 "actor" => actor.ap_id,
267 "object" => object.data["id"],
268 "to" => to,
269 "cc" => cc,
270 "context" => object.data["context"]
271 }, []}
272 end
273 end