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