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