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