builder.ex 9.16 KB
Newer Older
feld's avatar
feld committed
1
# Pleroma: A lightweight social networking server
Haelwenn's avatar
Haelwenn committed
2
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
feld's avatar
feld committed
3
4
# SPDX-License-Identifier: AGPL-3.0-only

5
6
7
8
9
10
11
defmodule Pleroma.Web.ActivityPub.Builder do
  @moduledoc """
  This module builds the objects. Meant to be used for creating local objects.

  This module encodes our addressing policies and general shape of our objects.
  """

lain's avatar
lain committed
12
  alias Pleroma.Emoji
lain's avatar
lain committed
13
14
  alias Pleroma.Object
  alias Pleroma.User
15
  alias Pleroma.Web.ActivityPub.Relay
16
17
  alias Pleroma.Web.ActivityPub.Utils
  alias Pleroma.Web.ActivityPub.Visibility
18
  alias Pleroma.Web.CommonAPI.ActivityDraft
19

20
21
  require Pleroma.Constants

22
  def accept_or_reject(actor, activity, type) do
23
24
25
    data = %{
      "id" => Utils.generate_activity_id(),
      "actor" => actor.ap_id,
26
27
28
      "type" => type,
      "object" => activity.data["id"],
      "to" => [activity.actor]
29
30
31
32
33
    }

    {:ok, data, []}
  end

34
35
36
37
38
39
40
41
42
43
  @spec reject(User.t(), Activity.t()) :: {:ok, map(), keyword()}
  def reject(actor, rejected_activity) do
    accept_or_reject(actor, rejected_activity, "Reject")
  end

  @spec accept(User.t(), Activity.t()) :: {:ok, map(), keyword()}
  def accept(actor, accepted_activity) do
    accept_or_reject(actor, accepted_activity, "Accept")
  end

lain's avatar
lain committed
44
45
46
47
48
49
50
51
52
53
54
55
56
  @spec follow(User.t(), User.t()) :: {:ok, map(), keyword()}
  def follow(follower, followed) do
    data = %{
      "id" => Utils.generate_activity_id(),
      "actor" => follower.ap_id,
      "type" => "Follow",
      "object" => followed.ap_id,
      "to" => [followed.ap_id]
    }

    {:ok, data, []}
  end

57
58
  @spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
  def emoji_react(actor, object, emoji) do
59
    with {:ok, data, meta} <- object_action(actor, object) do
60
61
62
63
64
65
66
67
68
      data =
        data
        |> Map.put("content", emoji)
        |> Map.put("type", "EmojiReact")

      {:ok, data, meta}
    end
  end

lain's avatar
lain committed
69
70
71
72
73
74
75
76
77
78
79
80
81
  @spec undo(User.t(), Activity.t()) :: {:ok, map(), keyword()}
  def undo(actor, object) do
    {:ok,
     %{
       "id" => Utils.generate_activity_id(),
       "actor" => actor.ap_id,
       "type" => "Undo",
       "object" => object.data["id"],
       "to" => object.data["to"] || [],
       "cc" => object.data["cc"] || []
     }, []}
  end

82
83
  @spec delete(User.t(), String.t()) :: {:ok, map(), keyword()}
  def delete(actor, object_id) do
84
    object = Object.normalize(object_id, fetch: false)
85

86
87
88
89
90
91
92
93
94
95
96
97
    user = !object && User.get_cached_by_ap_id(object_id)

    to =
      case {object, user} do
        {%Object{}, _} ->
          # We are deleting an object, address everyone who was originally mentioned
          (object.data["to"] || []) ++ (object.data["cc"] || [])

        {_, %User{follower_address: follower_address}} ->
          # We are deleting a user, address the followers of that user
          [follower_address]
      end
98
99
100
101
102
103
104
105
106
107
108

    {:ok,
     %{
       "id" => Utils.generate_activity_id(),
       "actor" => actor.ap_id,
       "object" => object_id,
       "to" => to,
       "type" => "Delete"
     }, []}
  end

lain's avatar
lain committed
109
  def create(actor, object, recipients) do
110
111
112
113
114
115
116
    context =
      if is_map(object) do
        object["context"]
      else
        nil
      end

117
118
119
120
121
    {:ok,
     %{
       "id" => Utils.generate_activity_id(),
       "actor" => actor.ap_id,
       "to" => recipients,
lain's avatar
lain committed
122
       "object" => object,
123
124
       "type" => "Create",
       "published" => DateTime.utc_now() |> DateTime.to_iso8601()
125
126
     }
     |> Pleroma.Maps.put_if_present("context", context), []}
127
128
  end

129
  @spec note(ActivityDraft.t()) :: {:ok, map(), keyword()}
130
  def note(%ActivityDraft{} = draft) do
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
    data =
      %{
        "type" => "Note",
        "to" => draft.to,
        "cc" => draft.cc,
        "content" => draft.content_html,
        "summary" => draft.summary,
        "sensitive" => draft.sensitive,
        "context" => draft.context,
        "attachment" => draft.attachments,
        "actor" => draft.user.ap_id,
        "tag" => Keyword.values(draft.tags) |> Enum.uniq()
      }
      |> add_in_reply_to(draft.in_reply_to)
      |> Map.merge(draft.extra)

    {:ok, data, []}
148
149
150
151
152
153
154
155
156
157
158
159
  end

  defp add_in_reply_to(object, nil), do: object

  defp add_in_reply_to(object, in_reply_to) do
    with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
      Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
    else
      _ -> object
    end
  end

lain's avatar
lain committed
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
  def chat_message(actor, recipient, content, opts \\ []) do
    basic = %{
      "id" => Utils.generate_object_id(),
      "actor" => actor.ap_id,
      "type" => "ChatMessage",
      "to" => [recipient],
      "content" => content,
      "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
      "emoji" => Emoji.Formatter.get_emoji_map(content)
    }

    case opts[:attachment] do
      %Object{data: attachment_data} ->
        {
          :ok,
          Map.put(basic, "attachment", attachment_data),
          []
        }

      _ ->
        {:ok, basic, []}
    end
182
183
  end

184
185
186
187
188
  def answer(user, object, name) do
    {:ok,
     %{
       "type" => "Answer",
       "actor" => user.ap_id,
189
       "attributedTo" => user.ap_id,
190
191
192
193
194
195
196
197
198
199
       "cc" => [object.data["actor"]],
       "to" => [],
       "name" => name,
       "inReplyTo" => object.data["id"],
       "context" => object.data["context"],
       "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
       "id" => Utils.generate_object_id()
     }, []}
  end

200
201
202
203
204
205
206
207
208
209
  @spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
  def tombstone(actor, id) do
    {:ok,
     %{
       "id" => id,
       "actor" => actor,
       "type" => "Tombstone"
     }, []}
  end

210
211
  @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
  def like(actor, object) do
212
213
214
215
216
217
218
219
220
    with {:ok, data, meta} <- object_action(actor, object) do
      data =
        data
        |> Map.put("type", "Like")

      {:ok, data, meta}
    end
  end

221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
  # Retricted to user updates for now, always public
  @spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
  def update(actor, object) do
    to = [Pleroma.Constants.as_public(), actor.follower_address]

    {:ok,
     %{
       "id" => Utils.generate_activity_id(),
       "type" => "Update",
       "actor" => actor.ap_id,
       "object" => object,
       "to" => to
     }, []}
  end

lain's avatar
lain committed
236
237
238
239
240
241
242
243
244
245
246
247
  @spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
  def block(blocker, blocked) do
    {:ok,
     %{
       "id" => Utils.generate_activity_id(),
       "type" => "Block",
       "actor" => blocker.ap_id,
       "object" => blocked.ap_id,
       "to" => [blocked.ap_id]
     }, []}
  end

248
  @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
249
250
  def announce(actor, object, options \\ []) do
    public? = Keyword.get(options, :public, false)
251

252
    to =
253
      cond do
Alexander Strizhakov's avatar
Alexander Strizhakov committed
254
        actor.ap_id == Relay.ap_id() ->
255
256
          [actor.follower_address]

minibikini's avatar
minibikini committed
257
        public? and Visibility.is_local_public?(object) ->
258
          [actor.follower_address, object.data["actor"], Utils.as_local_public()]
minibikini's avatar
minibikini committed
259

260
261
262
263
264
        public? ->
          [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]

        true ->
          [actor.follower_address, object.data["actor"]]
265
266
      end

267
268
269
270
271
272
273
    {:ok,
     %{
       "id" => Utils.generate_activity_id(),
       "actor" => actor.ap_id,
       "object" => object.data["id"],
       "to" => to,
       "context" => object.data["context"],
274
275
       "type" => "Announce",
       "published" => Utils.make_date()
276
277
278
     }, []}
  end

279
280
  @spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()}
  defp object_action(actor, object) do
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
    object_actor = User.get_cached_by_ap_id(object.data["actor"])

    # Address the actor of the object, and our actor's follower collection if the post is public.
    to =
      if Visibility.is_public?(object) do
        [actor.follower_address, object.data["actor"]]
      else
        [object.data["actor"]]
      end

    # CC everyone who's been addressed in the object, except ourself and the object actor's
    # follower collection
    cc =
      (object.data["to"] ++ (object.data["cc"] || []))
      |> List.delete(actor.ap_id)
      |> List.delete(object_actor.follower_address)

    {:ok,
     %{
       "id" => Utils.generate_activity_id(),
       "actor" => actor.ap_id,
       "object" => object.data["id"],
       "to" => to,
       "cc" => cc,
       "context" => object.data["context"]
     }, []}
  end
Alexander Strizhakov's avatar
Alexander Strizhakov committed
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339

  @spec pin(User.t(), Object.t()) :: {:ok, map(), keyword()}
  def pin(%User{} = user, object) do
    {:ok,
     %{
       "id" => Utils.generate_activity_id(),
       "target" => pinned_url(user.nickname),
       "object" => object.data["id"],
       "actor" => user.ap_id,
       "type" => "Add",
       "to" => [Pleroma.Constants.as_public()],
       "cc" => [user.follower_address]
     }, []}
  end

  @spec unpin(User.t(), Object.t()) :: {:ok, map, keyword()}
  def unpin(%User{} = user, object) do
    {:ok,
     %{
       "id" => Utils.generate_activity_id(),
       "target" => pinned_url(user.nickname),
       "object" => object.data["id"],
       "actor" => user.ap_id,
       "type" => "Remove",
       "to" => [Pleroma.Constants.as_public()],
       "cc" => [user.follower_address]
     }, []}
  end

  defp pinned_url(nickname) when is_binary(nickname) do
    Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)
  end
340
end