Merge branch 'develop' into global-status-expiration
[akkoma] / lib / pleroma / web / activity_pub / activity_pub.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.ActivityPub do
6 alias Pleroma.Activity
7 alias Pleroma.Activity.Ir.Topics
8 alias Pleroma.ActivityExpiration
9 alias Pleroma.Config
10 alias Pleroma.Constants
11 alias Pleroma.Conversation
12 alias Pleroma.Conversation.Participation
13 alias Pleroma.Notification
14 alias Pleroma.Object
15 alias Pleroma.Object.Containment
16 alias Pleroma.Object.Fetcher
17 alias Pleroma.Pagination
18 alias Pleroma.Repo
19 alias Pleroma.Upload
20 alias Pleroma.User
21 alias Pleroma.Web.ActivityPub.MRF
22 alias Pleroma.Web.ActivityPub.Transmogrifier
23 alias Pleroma.Web.ActivityPub.Utils
24 alias Pleroma.Web.Streamer
25 alias Pleroma.Web.WebFinger
26 alias Pleroma.Workers.BackgroundWorker
27
28 import Ecto.Query
29 import Pleroma.Web.ActivityPub.Utils
30 import Pleroma.Web.ActivityPub.Visibility
31
32 require Logger
33 require Pleroma.Constants
34
35 # For Announce activities, we filter the recipients based on following status for any actors
36 # that match actual users. See issue #164 for more information about why this is necessary.
37 defp get_recipients(%{"type" => "Announce"} = data) do
38 to = Map.get(data, "to", [])
39 cc = Map.get(data, "cc", [])
40 bcc = Map.get(data, "bcc", [])
41 actor = User.get_cached_by_ap_id(data["actor"])
42
43 recipients =
44 Enum.filter(Enum.concat([to, cc, bcc]), fn recipient ->
45 case User.get_cached_by_ap_id(recipient) do
46 nil -> true
47 user -> User.following?(user, actor)
48 end
49 end)
50
51 {recipients, to, cc}
52 end
53
54 defp get_recipients(%{"type" => "Create"} = data) do
55 to = Map.get(data, "to", [])
56 cc = Map.get(data, "cc", [])
57 bcc = Map.get(data, "bcc", [])
58 actor = Map.get(data, "actor", [])
59 recipients = [to, cc, bcc, [actor]] |> Enum.concat() |> Enum.uniq()
60 {recipients, to, cc}
61 end
62
63 defp get_recipients(data) do
64 to = Map.get(data, "to", [])
65 cc = Map.get(data, "cc", [])
66 bcc = Map.get(data, "bcc", [])
67 recipients = Enum.concat([to, cc, bcc])
68 {recipients, to, cc}
69 end
70
71 defp check_actor_is_active(actor) do
72 if not is_nil(actor) do
73 with user <- User.get_cached_by_ap_id(actor),
74 false <- user.deactivated do
75 true
76 else
77 _e -> false
78 end
79 else
80 true
81 end
82 end
83
84 defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do
85 limit = Config.get([:instance, :remote_limit])
86 String.length(content) <= limit
87 end
88
89 defp check_remote_limit(_), do: true
90
91 def increase_note_count_if_public(actor, object) do
92 if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
93 end
94
95 def decrease_note_count_if_public(actor, object) do
96 if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
97 end
98
99 def increase_replies_count_if_reply(%{
100 "object" => %{"inReplyTo" => reply_ap_id} = object,
101 "type" => "Create"
102 }) do
103 if is_public?(object) do
104 Object.increase_replies_count(reply_ap_id)
105 end
106 end
107
108 def increase_replies_count_if_reply(_create_data), do: :noop
109
110 def decrease_replies_count_if_reply(%Object{
111 data: %{"inReplyTo" => reply_ap_id} = object
112 }) do
113 if is_public?(object) do
114 Object.decrease_replies_count(reply_ap_id)
115 end
116 end
117
118 def decrease_replies_count_if_reply(_object), do: :noop
119
120 def increase_poll_votes_if_vote(%{
121 "object" => %{"inReplyTo" => reply_ap_id, "name" => name},
122 "type" => "Create",
123 "actor" => actor
124 }) do
125 Object.increase_vote_count(reply_ap_id, name, actor)
126 end
127
128 def increase_poll_votes_if_vote(_create_data), do: :noop
129
130 @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
131 def persist(object, meta) do
132 with local <- Keyword.fetch!(meta, :local),
133 {recipients, _, _} <- get_recipients(object),
134 {:ok, activity} <-
135 Repo.insert(%Activity{
136 data: object,
137 local: local,
138 recipients: recipients,
139 actor: object["actor"]
140 }) do
141 {:ok, activity, meta}
142 end
143 end
144
145 @spec insert(map(), boolean(), boolean(), boolean()) :: {:ok, Activity.t()} | {:error, any()}
146 def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do
147 with nil <- Activity.normalize(map),
148 map <- lazy_put_activity_defaults(map, fake),
149 true <- bypass_actor_check || check_actor_is_active(map["actor"]),
150 {_, true} <- {:remote_limit_error, check_remote_limit(map)},
151 {:ok, map} <- MRF.filter(map),
152 {recipients, _, _} = get_recipients(map),
153 {:fake, false, map, recipients} <- {:fake, fake, map, recipients},
154 {:containment, :ok} <- {:containment, Containment.contain_child(map)},
155 {:ok, map, object} <- insert_full_object(map) do
156 {:ok, activity} =
157 %Activity{
158 data: map,
159 local: local,
160 actor: map["actor"],
161 recipients: recipients
162 }
163 |> Repo.insert()
164 |> maybe_create_activity_expiration()
165
166 # Splice in the child object if we have one.
167 activity =
168 if not is_nil(object) do
169 Map.put(activity, :object, object)
170 else
171 activity
172 end
173
174 BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
175
176 Notification.create_notifications(activity)
177
178 conversation = create_or_bump_conversation(activity, map["actor"])
179 participations = get_participations(conversation)
180 stream_out(activity)
181 stream_out_participations(participations)
182 {:ok, activity}
183 else
184 %Activity{} = activity ->
185 {:ok, activity}
186
187 {:fake, true, map, recipients} ->
188 activity = %Activity{
189 data: map,
190 local: local,
191 actor: map["actor"],
192 recipients: recipients,
193 id: "pleroma:fakeid"
194 }
195
196 Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
197 {:ok, activity}
198
199 error ->
200 {:error, error}
201 end
202 end
203
204 defp maybe_create_activity_expiration({:ok, %{data: %{"expires_at" => expires_at}} = activity}) do
205 with {:ok, _} <- ActivityExpiration.create(activity, expires_at) do
206 {:ok, activity}
207 end
208 end
209
210 defp maybe_create_activity_expiration(result), do: result
211
212 defp create_or_bump_conversation(activity, actor) do
213 with {:ok, conversation} <- Conversation.create_or_bump_for(activity),
214 %User{} = user <- User.get_cached_by_ap_id(actor),
215 Participation.mark_as_read(user, conversation) do
216 {:ok, conversation}
217 end
218 end
219
220 defp get_participations({:ok, conversation}) do
221 conversation
222 |> Repo.preload(:participations, force: true)
223 |> Map.get(:participations)
224 end
225
226 defp get_participations(_), do: []
227
228 def stream_out_participations(participations) do
229 participations =
230 participations
231 |> Repo.preload(:user)
232
233 Streamer.stream("participation", participations)
234 end
235
236 def stream_out_participations(%Object{data: %{"context" => context}}, user) do
237 with %Conversation{} = conversation <- Conversation.get_for_ap_id(context),
238 conversation = Repo.preload(conversation, :participations),
239 last_activity_id =
240 fetch_latest_activity_id_for_context(conversation.ap_id, %{
241 "user" => user,
242 "blocking_user" => user
243 }) do
244 if last_activity_id do
245 stream_out_participations(conversation.participations)
246 end
247 end
248 end
249
250 def stream_out_participations(_, _), do: :noop
251
252 def stream_out(%Activity{data: %{"type" => data_type}} = activity)
253 when data_type in ["Create", "Announce", "Delete"] do
254 activity
255 |> Topics.get_activity_topics()
256 |> Streamer.stream(activity)
257 end
258
259 def stream_out(_activity) do
260 :noop
261 end
262
263 @spec create(map(), boolean()) :: {:ok, Activity.t()} | {:error, any()}
264 def create(params, fake \\ false) do
265 with {:ok, result} <- Repo.transaction(fn -> do_create(params, fake) end) do
266 result
267 end
268 end
269
270 defp do_create(%{to: to, actor: actor, context: context, object: object} = params, fake) do
271 additional = params[:additional] || %{}
272 # only accept false as false value
273 local = !(params[:local] == false)
274 published = params[:published]
275 quick_insert? = Config.get([:env]) == :benchmark
276
277 with create_data <-
278 make_create_data(
279 %{to: to, actor: actor, published: published, context: context, object: object},
280 additional
281 ),
282 {:ok, activity} <- insert(create_data, local, fake),
283 {:fake, false, activity} <- {:fake, fake, activity},
284 _ <- increase_replies_count_if_reply(create_data),
285 _ <- increase_poll_votes_if_vote(create_data),
286 {:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
287 {:ok, _actor} <- increase_note_count_if_public(actor, activity),
288 :ok <- maybe_federate(activity) do
289 {:ok, activity}
290 else
291 {:quick_insert, true, activity} ->
292 {:ok, activity}
293
294 {:fake, true, activity} ->
295 {:ok, activity}
296
297 {:error, message} ->
298 Repo.rollback(message)
299 end
300 end
301
302 @spec listen(map()) :: {:ok, Activity.t()} | {:error, any()}
303 def listen(%{to: to, actor: actor, context: context, object: object} = params) do
304 additional = params[:additional] || %{}
305 # only accept false as false value
306 local = !(params[:local] == false)
307 published = params[:published]
308
309 with listen_data <-
310 make_listen_data(
311 %{to: to, actor: actor, published: published, context: context, object: object},
312 additional
313 ),
314 {:ok, activity} <- insert(listen_data, local),
315 :ok <- maybe_federate(activity) do
316 {:ok, activity}
317 end
318 end
319
320 @spec accept(map()) :: {:ok, Activity.t()} | {:error, any()}
321 def accept(params) do
322 accept_or_reject("Accept", params)
323 end
324
325 @spec reject(map()) :: {:ok, Activity.t()} | {:error, any()}
326 def reject(params) do
327 accept_or_reject("Reject", params)
328 end
329
330 @spec accept_or_reject(String.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
331 def accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do
332 local = Map.get(params, :local, true)
333 activity_id = Map.get(params, :activity_id, nil)
334
335 with data <-
336 %{"to" => to, "type" => type, "actor" => actor.ap_id, "object" => object}
337 |> Utils.maybe_put("id", activity_id),
338 {:ok, activity} <- insert(data, local),
339 :ok <- maybe_federate(activity) do
340 {:ok, activity}
341 end
342 end
343
344 @spec update(map()) :: {:ok, Activity.t()} | {:error, any()}
345 def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
346 local = !(params[:local] == false)
347 activity_id = params[:activity_id]
348
349 with data <- %{
350 "to" => to,
351 "cc" => cc,
352 "type" => "Update",
353 "actor" => actor,
354 "object" => object
355 },
356 data <- Utils.maybe_put(data, "id", activity_id),
357 {:ok, activity} <- insert(data, local),
358 :ok <- maybe_federate(activity) do
359 {:ok, activity}
360 end
361 end
362
363 @spec react_with_emoji(User.t(), Object.t(), String.t(), keyword()) ::
364 {:ok, Activity.t(), Object.t()} | {:error, any()}
365 def react_with_emoji(user, object, emoji, options \\ []) do
366 with {:ok, result} <-
367 Repo.transaction(fn -> do_react_with_emoji(user, object, emoji, options) end) do
368 result
369 end
370 end
371
372 defp do_react_with_emoji(user, object, emoji, options) do
373 with local <- Keyword.get(options, :local, true),
374 activity_id <- Keyword.get(options, :activity_id, nil),
375 true <- Pleroma.Emoji.is_unicode_emoji?(emoji),
376 reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id),
377 {:ok, activity} <- insert(reaction_data, local),
378 {:ok, object} <- add_emoji_reaction_to_object(activity, object),
379 :ok <- maybe_federate(activity) do
380 {:ok, activity, object}
381 else
382 false -> {:error, false}
383 {:error, error} -> Repo.rollback(error)
384 end
385 end
386
387 @spec unreact_with_emoji(User.t(), String.t(), keyword()) ::
388 {:ok, Activity.t(), Object.t()} | {:error, any()}
389 def unreact_with_emoji(user, reaction_id, options \\ []) do
390 with {:ok, result} <-
391 Repo.transaction(fn -> do_unreact_with_emoji(user, reaction_id, options) end) do
392 result
393 end
394 end
395
396 defp do_unreact_with_emoji(user, reaction_id, options) do
397 with local <- Keyword.get(options, :local, true),
398 activity_id <- Keyword.get(options, :activity_id, nil),
399 user_ap_id <- user.ap_id,
400 %Activity{actor: ^user_ap_id} = reaction_activity <- Activity.get_by_ap_id(reaction_id),
401 object <- Object.normalize(reaction_activity),
402 unreact_data <- make_undo_data(user, reaction_activity, activity_id),
403 {:ok, activity} <- insert(unreact_data, local),
404 {:ok, object} <- remove_emoji_reaction_from_object(reaction_activity, object),
405 :ok <- maybe_federate(activity) do
406 {:ok, activity, object}
407 else
408 {:error, error} -> Repo.rollback(error)
409 end
410 end
411
412 @spec unlike(User.t(), Object.t(), String.t() | nil, boolean()) ::
413 {:ok, Activity.t(), Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()}
414 def unlike(%User{} = actor, %Object{} = object, activity_id \\ nil, local \\ true) do
415 with {:ok, result} <-
416 Repo.transaction(fn -> do_unlike(actor, object, activity_id, local) end) do
417 result
418 end
419 end
420
421 defp do_unlike(actor, object, activity_id, local) do
422 with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),
423 unlike_data <- make_unlike_data(actor, like_activity, activity_id),
424 {:ok, unlike_activity} <- insert(unlike_data, local),
425 {:ok, _activity} <- Repo.delete(like_activity),
426 {:ok, object} <- remove_like_from_object(like_activity, object),
427 :ok <- maybe_federate(unlike_activity) do
428 {:ok, unlike_activity, like_activity, object}
429 else
430 nil -> {:ok, object}
431 {:error, error} -> Repo.rollback(error)
432 end
433 end
434
435 @spec announce(User.t(), Object.t(), String.t() | nil, boolean(), boolean()) ::
436 {:ok, Activity.t(), Object.t()} | {:error, any()}
437 def announce(
438 %User{ap_id: _} = user,
439 %Object{data: %{"id" => _}} = object,
440 activity_id \\ nil,
441 local \\ true,
442 public \\ true
443 ) do
444 with {:ok, result} <-
445 Repo.transaction(fn -> do_announce(user, object, activity_id, local, public) end) do
446 result
447 end
448 end
449
450 defp do_announce(user, object, activity_id, local, public) do
451 with true <- is_announceable?(object, user, public),
452 object <- Object.get_by_id(object.id),
453 announce_data <- make_announce_data(user, object, activity_id, public),
454 {:ok, activity} <- insert(announce_data, local),
455 {:ok, object} <- add_announce_to_object(activity, object),
456 :ok <- maybe_federate(activity) do
457 {:ok, activity, object}
458 else
459 false -> {:error, false}
460 {:error, error} -> Repo.rollback(error)
461 end
462 end
463
464 @spec unannounce(User.t(), Object.t(), String.t() | nil, boolean()) ::
465 {:ok, Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()}
466 def unannounce(
467 %User{} = actor,
468 %Object{} = object,
469 activity_id \\ nil,
470 local \\ true
471 ) do
472 with {:ok, result} <-
473 Repo.transaction(fn -> do_unannounce(actor, object, activity_id, local) end) do
474 result
475 end
476 end
477
478 defp do_unannounce(actor, object, activity_id, local) do
479 with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
480 unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
481 {:ok, unannounce_activity} <- insert(unannounce_data, local),
482 :ok <- maybe_federate(unannounce_activity),
483 {:ok, _activity} <- Repo.delete(announce_activity),
484 {:ok, object} <- remove_announce_from_object(announce_activity, object) do
485 {:ok, unannounce_activity, object}
486 else
487 nil -> {:ok, object}
488 {:error, error} -> Repo.rollback(error)
489 end
490 end
491
492 @spec follow(User.t(), User.t(), String.t() | nil, boolean()) ::
493 {:ok, Activity.t()} | {:error, any()}
494 def follow(follower, followed, activity_id \\ nil, local \\ true) do
495 with {:ok, result} <-
496 Repo.transaction(fn -> do_follow(follower, followed, activity_id, local) end) do
497 result
498 end
499 end
500
501 defp do_follow(follower, followed, activity_id, local) do
502 with data <- make_follow_data(follower, followed, activity_id),
503 {:ok, activity} <- insert(data, local),
504 :ok <- maybe_federate(activity) do
505 {:ok, activity}
506 else
507 {:error, error} -> Repo.rollback(error)
508 end
509 end
510
511 @spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) ::
512 {:ok, Activity.t()} | nil | {:error, any()}
513 def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
514 with {:ok, result} <-
515 Repo.transaction(fn -> do_unfollow(follower, followed, activity_id, local) end) do
516 result
517 end
518 end
519
520 defp do_unfollow(follower, followed, activity_id, local) do
521 with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
522 {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
523 unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
524 {:ok, activity} <- insert(unfollow_data, local),
525 :ok <- maybe_federate(activity) do
526 {:ok, activity}
527 else
528 nil -> nil
529 {:error, error} -> Repo.rollback(error)
530 end
531 end
532
533 @spec delete(User.t() | Object.t(), keyword()) :: {:ok, User.t() | Object.t()} | {:error, any()}
534 def delete(entity, options \\ []) do
535 with {:ok, result} <- Repo.transaction(fn -> do_delete(entity, options) end) do
536 result
537 end
538 end
539
540 defp do_delete(%User{ap_id: ap_id, follower_address: follower_address} = user, _) do
541 with data <- %{
542 "to" => [follower_address],
543 "type" => "Delete",
544 "actor" => ap_id,
545 "object" => %{"type" => "Person", "id" => ap_id}
546 },
547 {:ok, activity} <- insert(data, true, true, true),
548 :ok <- maybe_federate(activity) do
549 {:ok, user}
550 end
551 end
552
553 defp do_delete(%Object{data: %{"id" => id, "actor" => actor}} = object, options) do
554 local = Keyword.get(options, :local, true)
555 activity_id = Keyword.get(options, :activity_id, nil)
556 actor = Keyword.get(options, :actor, actor)
557
558 user = User.get_cached_by_ap_id(actor)
559 to = (object.data["to"] || []) ++ (object.data["cc"] || [])
560
561 with create_activity <- Activity.get_create_by_object_ap_id(id),
562 data <-
563 %{
564 "type" => "Delete",
565 "actor" => actor,
566 "object" => id,
567 "to" => to,
568 "deleted_activity_id" => create_activity && create_activity.id
569 }
570 |> maybe_put("id", activity_id),
571 {:ok, activity} <- insert(data, local, false),
572 {:ok, object, _create_activity} <- Object.delete(object),
573 stream_out_participations(object, user),
574 _ <- decrease_replies_count_if_reply(object),
575 {:ok, _actor} <- decrease_note_count_if_public(user, object),
576 :ok <- maybe_federate(activity) do
577 {:ok, activity}
578 else
579 {:error, error} ->
580 Repo.rollback(error)
581 end
582 end
583
584 defp do_delete(%Object{data: %{"type" => "Tombstone", "id" => ap_id}}, _) do
585 activity =
586 ap_id
587 |> Activity.Queries.by_object_id()
588 |> Activity.Queries.by_type("Delete")
589 |> Repo.one()
590
591 {:ok, activity}
592 end
593
594 @spec block(User.t(), User.t(), String.t() | nil, boolean()) ::
595 {:ok, Activity.t()} | {:error, any()}
596 def block(blocker, blocked, activity_id \\ nil, local \\ true) do
597 with {:ok, result} <-
598 Repo.transaction(fn -> do_block(blocker, blocked, activity_id, local) end) do
599 result
600 end
601 end
602
603 defp do_block(blocker, blocked, activity_id, local) do
604 outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
605 unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
606
607 if unfollow_blocked do
608 follow_activity = fetch_latest_follow(blocker, blocked)
609 if follow_activity, do: unfollow(blocker, blocked, nil, local)
610 end
611
612 with true <- outgoing_blocks,
613 block_data <- make_block_data(blocker, blocked, activity_id),
614 {:ok, activity} <- insert(block_data, local),
615 :ok <- maybe_federate(activity) do
616 {:ok, activity}
617 else
618 {:error, error} -> Repo.rollback(error)
619 end
620 end
621
622 @spec unblock(User.t(), User.t(), String.t() | nil, boolean()) ::
623 {:ok, Activity.t()} | {:error, any()} | nil
624 def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
625 with {:ok, result} <-
626 Repo.transaction(fn -> do_unblock(blocker, blocked, activity_id, local) end) do
627 result
628 end
629 end
630
631 defp do_unblock(blocker, blocked, activity_id, local) do
632 with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
633 unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
634 {:ok, activity} <- insert(unblock_data, local),
635 :ok <- maybe_federate(activity) do
636 {:ok, activity}
637 else
638 nil -> nil
639 {:error, error} -> Repo.rollback(error)
640 end
641 end
642
643 @spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}
644 def flag(
645 %{
646 actor: actor,
647 context: _context,
648 account: account,
649 statuses: statuses,
650 content: content
651 } = params
652 ) do
653 # only accept false as false value
654 local = !(params[:local] == false)
655 forward = !(params[:forward] == false)
656
657 additional = params[:additional] || %{}
658
659 additional =
660 if forward do
661 Map.merge(additional, %{"to" => [], "cc" => [account.ap_id]})
662 else
663 Map.merge(additional, %{"to" => [], "cc" => []})
664 end
665
666 with flag_data <- make_flag_data(params, additional),
667 {:ok, activity} <- insert(flag_data, local),
668 {:ok, stripped_activity} <- strip_report_status_data(activity),
669 :ok <- maybe_federate(stripped_activity) do
670 User.all_superusers()
671 |> Enum.filter(fn user -> not is_nil(user.email) end)
672 |> Enum.each(fn superuser ->
673 superuser
674 |> Pleroma.Emails.AdminEmail.report(actor, account, statuses, content)
675 |> Pleroma.Emails.Mailer.deliver_async()
676 end)
677
678 {:ok, activity}
679 end
680 end
681
682 @spec move(User.t(), User.t(), boolean()) :: {:ok, Activity.t()} | {:error, any()}
683 def move(%User{} = origin, %User{} = target, local \\ true) do
684 params = %{
685 "type" => "Move",
686 "actor" => origin.ap_id,
687 "object" => origin.ap_id,
688 "target" => target.ap_id
689 }
690
691 with true <- origin.ap_id in target.also_known_as,
692 {:ok, activity} <- insert(params, local) do
693 maybe_federate(activity)
694
695 BackgroundWorker.enqueue("move_following", %{
696 "origin_id" => origin.id,
697 "target_id" => target.id
698 })
699
700 {:ok, activity}
701 else
702 false -> {:error, "Target account must have the origin in `alsoKnownAs`"}
703 err -> err
704 end
705 end
706
707 def fetch_activities_for_context_query(context, opts) do
708 public = [Constants.as_public()]
709
710 recipients =
711 if opts["user"],
712 do: [opts["user"].ap_id | User.following(opts["user"])] ++ public,
713 else: public
714
715 from(activity in Activity)
716 |> maybe_preload_objects(opts)
717 |> maybe_preload_bookmarks(opts)
718 |> maybe_set_thread_muted_field(opts)
719 |> restrict_blocked(opts)
720 |> restrict_recipients(recipients, opts["user"])
721 |> where(
722 [activity],
723 fragment(
724 "?->>'type' = ? and ?->>'context' = ?",
725 activity.data,
726 "Create",
727 activity.data,
728 ^context
729 )
730 )
731 |> exclude_poll_votes(opts)
732 |> exclude_id(opts)
733 |> order_by([activity], desc: activity.id)
734 end
735
736 @spec fetch_activities_for_context(String.t(), keyword() | map()) :: [Activity.t()]
737 def fetch_activities_for_context(context, opts \\ %{}) do
738 context
739 |> fetch_activities_for_context_query(opts)
740 |> Repo.all()
741 end
742
743 @spec fetch_latest_activity_id_for_context(String.t(), keyword() | map()) ::
744 FlakeId.Ecto.CompatType.t() | nil
745 def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
746 context
747 |> fetch_activities_for_context_query(Map.merge(%{"skip_preload" => true}, opts))
748 |> limit(1)
749 |> select([a], a.id)
750 |> Repo.one()
751 end
752
753 @spec fetch_public_activities(map(), Pagination.type()) :: [Activity.t()]
754 def fetch_public_activities(opts \\ %{}, pagination \\ :keyset) do
755 opts = Map.drop(opts, ["user"])
756
757 [Constants.as_public()]
758 |> fetch_activities_query(opts)
759 |> restrict_unlisted()
760 |> Pagination.fetch_paginated(opts, pagination)
761 end
762
763 @valid_visibilities ~w[direct unlisted public private]
764
765 defp restrict_visibility(query, %{visibility: visibility})
766 when is_list(visibility) do
767 if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
768 query =
769 from(
770 a in query,
771 where:
772 fragment(
773 "activity_visibility(?, ?, ?) = ANY (?)",
774 a.actor,
775 a.recipients,
776 a.data,
777 ^visibility
778 )
779 )
780
781 query
782 else
783 Logger.error("Could not restrict visibility to #{visibility}")
784 end
785 end
786
787 defp restrict_visibility(query, %{visibility: visibility})
788 when visibility in @valid_visibilities do
789 from(
790 a in query,
791 where:
792 fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
793 )
794 end
795
796 defp restrict_visibility(_query, %{visibility: visibility})
797 when visibility not in @valid_visibilities do
798 Logger.error("Could not restrict visibility to #{visibility}")
799 end
800
801 defp restrict_visibility(query, _visibility), do: query
802
803 defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
804 when is_list(visibility) do
805 if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
806 from(
807 a in query,
808 where:
809 not fragment(
810 "activity_visibility(?, ?, ?) = ANY (?)",
811 a.actor,
812 a.recipients,
813 a.data,
814 ^visibility
815 )
816 )
817 else
818 Logger.error("Could not exclude visibility to #{visibility}")
819 query
820 end
821 end
822
823 defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
824 when visibility in @valid_visibilities do
825 from(
826 a in query,
827 where:
828 not fragment(
829 "activity_visibility(?, ?, ?) = ?",
830 a.actor,
831 a.recipients,
832 a.data,
833 ^visibility
834 )
835 )
836 end
837
838 defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
839 when visibility not in [nil | @valid_visibilities] do
840 Logger.error("Could not exclude visibility to #{visibility}")
841 query
842 end
843
844 defp exclude_visibility(query, _visibility), do: query
845
846 defp restrict_thread_visibility(query, _, %{skip_thread_containment: true} = _),
847 do: query
848
849 defp restrict_thread_visibility(
850 query,
851 %{"user" => %User{skip_thread_containment: true}},
852 _
853 ),
854 do: query
855
856 defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}, _) do
857 from(
858 a in query,
859 where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
860 )
861 end
862
863 defp restrict_thread_visibility(query, _, _), do: query
864
865 def fetch_user_abstract_activities(user, reading_user, params \\ %{}) do
866 params =
867 params
868 |> Map.put("user", reading_user)
869 |> Map.put("actor_id", user.ap_id)
870
871 recipients =
872 user_activities_recipients(%{
873 "godmode" => params["godmode"],
874 "reading_user" => reading_user
875 })
876
877 fetch_activities(recipients, params)
878 |> Enum.reverse()
879 end
880
881 def fetch_user_activities(user, reading_user, params \\ %{}) do
882 params =
883 params
884 |> Map.put("type", ["Create", "Announce"])
885 |> Map.put("user", reading_user)
886 |> Map.put("actor_id", user.ap_id)
887 |> Map.put("pinned_activity_ids", user.pinned_activities)
888
889 params =
890 if User.blocks?(reading_user, user) do
891 params
892 else
893 params
894 |> Map.put("blocking_user", reading_user)
895 |> Map.put("muting_user", reading_user)
896 end
897
898 recipients =
899 user_activities_recipients(%{
900 "godmode" => params["godmode"],
901 "reading_user" => reading_user
902 })
903
904 fetch_activities(recipients, params)
905 |> Enum.reverse()
906 end
907
908 def fetch_statuses(reading_user, params) do
909 params =
910 params
911 |> Map.put("type", ["Create", "Announce"])
912
913 recipients =
914 user_activities_recipients(%{
915 "godmode" => params["godmode"],
916 "reading_user" => reading_user
917 })
918
919 fetch_activities(recipients, params, :offset)
920 |> Enum.reverse()
921 end
922
923 defp user_activities_recipients(%{"godmode" => true}) do
924 []
925 end
926
927 defp user_activities_recipients(%{"reading_user" => reading_user}) do
928 if reading_user do
929 [Constants.as_public()] ++ [reading_user.ap_id | User.following(reading_user)]
930 else
931 [Constants.as_public()]
932 end
933 end
934
935 defp restrict_since(query, %{"since_id" => ""}), do: query
936
937 defp restrict_since(query, %{"since_id" => since_id}) do
938 from(activity in query, where: activity.id > ^since_id)
939 end
940
941 defp restrict_since(query, _), do: query
942
943 defp restrict_tag_reject(_query, %{"tag_reject" => _tag_reject, "skip_preload" => true}) do
944 raise "Can't use the child object without preloading!"
945 end
946
947 defp restrict_tag_reject(query, %{"tag_reject" => tag_reject})
948 when is_list(tag_reject) and tag_reject != [] do
949 from(
950 [_activity, object] in query,
951 where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
952 )
953 end
954
955 defp restrict_tag_reject(query, _), do: query
956
957 defp restrict_tag_all(_query, %{"tag_all" => _tag_all, "skip_preload" => true}) do
958 raise "Can't use the child object without preloading!"
959 end
960
961 defp restrict_tag_all(query, %{"tag_all" => tag_all})
962 when is_list(tag_all) and tag_all != [] do
963 from(
964 [_activity, object] in query,
965 where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
966 )
967 end
968
969 defp restrict_tag_all(query, _), do: query
970
971 defp restrict_tag(_query, %{"tag" => _tag, "skip_preload" => true}) do
972 raise "Can't use the child object without preloading!"
973 end
974
975 defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do
976 from(
977 [_activity, object] in query,
978 where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag)
979 )
980 end
981
982 defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do
983 from(
984 [_activity, object] in query,
985 where: fragment("(?)->'tag' \\? (?)", object.data, ^tag)
986 )
987 end
988
989 defp restrict_tag(query, _), do: query
990
991 defp restrict_recipients(query, [], _user), do: query
992
993 defp restrict_recipients(query, recipients, nil) do
994 from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
995 end
996
997 defp restrict_recipients(query, recipients, user) do
998 from(
999 activity in query,
1000 where: fragment("? && ?", ^recipients, activity.recipients),
1001 or_where: activity.actor == ^user.ap_id
1002 )
1003 end
1004
1005 defp restrict_local(query, %{"local_only" => true}) do
1006 from(activity in query, where: activity.local == true)
1007 end
1008
1009 defp restrict_local(query, _), do: query
1010
1011 defp restrict_actor(query, %{"actor_id" => actor_id}) do
1012 from(activity in query, where: activity.actor == ^actor_id)
1013 end
1014
1015 defp restrict_actor(query, _), do: query
1016
1017 defp restrict_type(query, %{"type" => type}) when is_binary(type) do
1018 from(activity in query, where: fragment("?->>'type' = ?", activity.data, ^type))
1019 end
1020
1021 defp restrict_type(query, %{"type" => type}) do
1022 from(activity in query, where: fragment("?->>'type' = ANY(?)", activity.data, ^type))
1023 end
1024
1025 defp restrict_type(query, _), do: query
1026
1027 defp restrict_state(query, %{"state" => state}) do
1028 from(activity in query, where: fragment("?->>'state' = ?", activity.data, ^state))
1029 end
1030
1031 defp restrict_state(query, _), do: query
1032
1033 defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
1034 from(
1035 [_activity, object] in query,
1036 where: fragment("(?)->'likes' \\? (?)", object.data, ^ap_id)
1037 )
1038 end
1039
1040 defp restrict_favorited_by(query, _), do: query
1041
1042 defp restrict_media(_query, %{"only_media" => _val, "skip_preload" => true}) do
1043 raise "Can't use the child object without preloading!"
1044 end
1045
1046 defp restrict_media(query, %{"only_media" => val}) when val in [true, "true", "1"] do
1047 from(
1048 [_activity, object] in query,
1049 where: fragment("not (?)->'attachment' = (?)", object.data, ^[])
1050 )
1051 end
1052
1053 defp restrict_media(query, _), do: query
1054
1055 defp restrict_replies(query, %{"exclude_replies" => val}) when val in [true, "true", "1"] do
1056 from(
1057 [_activity, object] in query,
1058 where: fragment("?->>'inReplyTo' is null", object.data)
1059 )
1060 end
1061
1062 defp restrict_replies(query, %{
1063 "reply_filtering_user" => user,
1064 "reply_visibility" => "self"
1065 }) do
1066 from(
1067 [activity, object] in query,
1068 where:
1069 fragment(
1070 "?->>'inReplyTo' is null OR ? = ANY(?)",
1071 object.data,
1072 ^user.ap_id,
1073 activity.recipients
1074 )
1075 )
1076 end
1077
1078 defp restrict_replies(query, %{
1079 "reply_filtering_user" => user,
1080 "reply_visibility" => "following"
1081 }) do
1082 from(
1083 [activity, object] in query,
1084 where:
1085 fragment(
1086 "?->>'inReplyTo' is null OR ? && array_remove(?, ?) OR ? = ?",
1087 object.data,
1088 ^[user.ap_id | User.get_cached_user_friends_ap_ids(user)],
1089 activity.recipients,
1090 activity.actor,
1091 activity.actor,
1092 ^user.ap_id
1093 )
1094 )
1095 end
1096
1097 defp restrict_replies(query, _), do: query
1098
1099 defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val in [true, "true", "1"] do
1100 from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
1101 end
1102
1103 defp restrict_reblogs(query, _), do: query
1104
1105 defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
1106
1107 defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do
1108 mutes = opts["muted_users_ap_ids"] || User.muted_users_ap_ids(user)
1109
1110 query =
1111 from([activity] in query,
1112 where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
1113 where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
1114 )
1115
1116 unless opts["skip_preload"] do
1117 from([thread_mute: tm] in query, where: is_nil(tm.user_id))
1118 else
1119 query
1120 end
1121 end
1122
1123 defp restrict_muted(query, _), do: query
1124
1125 defp restrict_blocked(query, %{"blocking_user" => %User{} = user} = opts) do
1126 blocked_ap_ids = opts["blocked_users_ap_ids"] || User.blocked_users_ap_ids(user)
1127 domain_blocks = user.domain_blocks || []
1128
1129 following_ap_ids = User.get_friends_ap_ids(user)
1130
1131 query =
1132 if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query)
1133
1134 from(
1135 [activity, object: o] in query,
1136 where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
1137 where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids),
1138 where:
1139 fragment(
1140 "not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
1141 activity.data,
1142 activity.data,
1143 ^blocked_ap_ids
1144 ),
1145 where:
1146 fragment(
1147 "(not (split_part(?, '/', 3) = ANY(?))) or ? = ANY(?)",
1148 activity.actor,
1149 ^domain_blocks,
1150 activity.actor,
1151 ^following_ap_ids
1152 ),
1153 where:
1154 fragment(
1155 "(not (split_part(?->>'actor', '/', 3) = ANY(?))) or (?->>'actor') = ANY(?)",
1156 o.data,
1157 ^domain_blocks,
1158 o.data,
1159 ^following_ap_ids
1160 )
1161 )
1162 end
1163
1164 defp restrict_blocked(query, _), do: query
1165
1166 defp restrict_unlisted(query) do
1167 from(
1168 activity in query,
1169 where:
1170 fragment(
1171 "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
1172 activity.data,
1173 ^[Constants.as_public()]
1174 )
1175 )
1176 end
1177
1178 # TODO: when all endpoints migrated to OpenAPI compare `pinned` with `true` (boolean) only,
1179 # the same for `restrict_media/2`, `restrict_replies/2`, 'restrict_reblogs/2'
1180 # and `restrict_muted/2`
1181
1182 defp restrict_pinned(query, %{"pinned" => pinned, "pinned_activity_ids" => ids})
1183 when pinned in [true, "true", "1"] do
1184 from(activity in query, where: activity.id in ^ids)
1185 end
1186
1187 defp restrict_pinned(query, _), do: query
1188
1189 defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user} = opts) do
1190 muted_reblogs = opts["reblog_muted_users_ap_ids"] || User.reblog_muted_users_ap_ids(user)
1191
1192 from(
1193 activity in query,
1194 where:
1195 fragment(
1196 "not ( ?->>'type' = 'Announce' and ? = ANY(?))",
1197 activity.data,
1198 activity.actor,
1199 ^muted_reblogs
1200 )
1201 )
1202 end
1203
1204 defp restrict_muted_reblogs(query, _), do: query
1205
1206 defp restrict_instance(query, %{"instance" => instance}) do
1207 users =
1208 from(
1209 u in User,
1210 select: u.ap_id,
1211 where: fragment("? LIKE ?", u.nickname, ^"%@#{instance}")
1212 )
1213 |> Repo.all()
1214
1215 from(activity in query, where: activity.actor in ^users)
1216 end
1217
1218 defp restrict_instance(query, _), do: query
1219
1220 defp exclude_poll_votes(query, %{"include_poll_votes" => true}), do: query
1221
1222 defp exclude_poll_votes(query, _) do
1223 if has_named_binding?(query, :object) do
1224 from([activity, object: o] in query,
1225 where: fragment("not(?->>'type' = ?)", o.data, "Answer")
1226 )
1227 else
1228 query
1229 end
1230 end
1231
1232 defp exclude_id(query, %{"exclude_id" => id}) when is_binary(id) do
1233 from(activity in query, where: activity.id != ^id)
1234 end
1235
1236 defp exclude_id(query, _), do: query
1237
1238 defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
1239
1240 defp maybe_preload_objects(query, _) do
1241 query
1242 |> Activity.with_preloaded_object()
1243 end
1244
1245 defp maybe_preload_bookmarks(query, %{"skip_preload" => true}), do: query
1246
1247 defp maybe_preload_bookmarks(query, opts) do
1248 query
1249 |> Activity.with_preloaded_bookmark(opts["user"])
1250 end
1251
1252 defp maybe_preload_report_notes(query, %{"preload_report_notes" => true}) do
1253 query
1254 |> Activity.with_preloaded_report_notes()
1255 end
1256
1257 defp maybe_preload_report_notes(query, _), do: query
1258
1259 defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query
1260
1261 defp maybe_set_thread_muted_field(query, opts) do
1262 query
1263 |> Activity.with_set_thread_muted_field(opts["muting_user"] || opts["user"])
1264 end
1265
1266 defp maybe_order(query, %{order: :desc}) do
1267 query
1268 |> order_by(desc: :id)
1269 end
1270
1271 defp maybe_order(query, %{order: :asc}) do
1272 query
1273 |> order_by(asc: :id)
1274 end
1275
1276 defp maybe_order(query, _), do: query
1277
1278 defp fetch_activities_query_ap_ids_ops(opts) do
1279 source_user = opts["muting_user"]
1280 ap_id_relationships = if source_user, do: [:mute, :reblog_mute], else: []
1281
1282 ap_id_relationships =
1283 ap_id_relationships ++
1284 if opts["blocking_user"] && opts["blocking_user"] == source_user do
1285 [:block]
1286 else
1287 []
1288 end
1289
1290 preloaded_ap_ids = User.outgoing_relationships_ap_ids(source_user, ap_id_relationships)
1291
1292 restrict_blocked_opts = Map.merge(%{"blocked_users_ap_ids" => preloaded_ap_ids[:block]}, opts)
1293 restrict_muted_opts = Map.merge(%{"muted_users_ap_ids" => preloaded_ap_ids[:mute]}, opts)
1294
1295 restrict_muted_reblogs_opts =
1296 Map.merge(%{"reblog_muted_users_ap_ids" => preloaded_ap_ids[:reblog_mute]}, opts)
1297
1298 {restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts}
1299 end
1300
1301 def fetch_activities_query(recipients, opts \\ %{}) do
1302 {restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts} =
1303 fetch_activities_query_ap_ids_ops(opts)
1304
1305 config = %{
1306 skip_thread_containment: Config.get([:instance, :skip_thread_containment])
1307 }
1308
1309 Activity
1310 |> maybe_preload_objects(opts)
1311 |> maybe_preload_bookmarks(opts)
1312 |> maybe_preload_report_notes(opts)
1313 |> maybe_set_thread_muted_field(opts)
1314 |> maybe_order(opts)
1315 |> restrict_recipients(recipients, opts["user"])
1316 |> restrict_replies(opts)
1317 |> restrict_tag(opts)
1318 |> restrict_tag_reject(opts)
1319 |> restrict_tag_all(opts)
1320 |> restrict_since(opts)
1321 |> restrict_local(opts)
1322 |> restrict_actor(opts)
1323 |> restrict_type(opts)
1324 |> restrict_state(opts)
1325 |> restrict_favorited_by(opts)
1326 |> restrict_blocked(restrict_blocked_opts)
1327 |> restrict_muted(restrict_muted_opts)
1328 |> restrict_media(opts)
1329 |> restrict_visibility(opts)
1330 |> restrict_thread_visibility(opts, config)
1331 |> restrict_reblogs(opts)
1332 |> restrict_pinned(opts)
1333 |> restrict_muted_reblogs(restrict_muted_reblogs_opts)
1334 |> restrict_instance(opts)
1335 |> Activity.restrict_deactivated_users()
1336 |> exclude_poll_votes(opts)
1337 |> exclude_visibility(opts)
1338 end
1339
1340 def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
1341 list_memberships = Pleroma.List.memberships(opts["user"])
1342
1343 fetch_activities_query(recipients ++ list_memberships, opts)
1344 |> Pagination.fetch_paginated(opts, pagination)
1345 |> Enum.reverse()
1346 |> maybe_update_cc(list_memberships, opts["user"])
1347 end
1348
1349 @doc """
1350 Fetch favorites activities of user with order by sort adds to favorites
1351 """
1352 @spec fetch_favourites(User.t(), map(), Pagination.type()) :: list(Activity.t())
1353 def fetch_favourites(user, params \\ %{}, pagination \\ :keyset) do
1354 user.ap_id
1355 |> Activity.Queries.by_actor()
1356 |> Activity.Queries.by_type("Like")
1357 |> Activity.with_joined_object()
1358 |> Object.with_joined_activity()
1359 |> select([_like, object, activity], %{activity | object: object})
1360 |> order_by([like, _, _], desc: like.id)
1361 |> Pagination.fetch_paginated(
1362 Map.merge(params, %{"skip_order" => true}),
1363 pagination,
1364 :object_activity
1365 )
1366 end
1367
1368 defp maybe_update_cc(activities, list_memberships, %User{ap_id: user_ap_id})
1369 when is_list(list_memberships) and length(list_memberships) > 0 do
1370 Enum.map(activities, fn
1371 %{data: %{"bcc" => bcc}} = activity when is_list(bcc) and length(bcc) > 0 ->
1372 if Enum.any?(bcc, &(&1 in list_memberships)) do
1373 update_in(activity.data["cc"], &[user_ap_id | &1])
1374 else
1375 activity
1376 end
1377
1378 activity ->
1379 activity
1380 end)
1381 end
1382
1383 defp maybe_update_cc(activities, _, _), do: activities
1384
1385 def fetch_activities_bounded_query(query, recipients, recipients_with_public) do
1386 from(activity in query,
1387 where:
1388 fragment("? && ?", activity.recipients, ^recipients) or
1389 (fragment("? && ?", activity.recipients, ^recipients_with_public) and
1390 ^Constants.as_public() in activity.recipients)
1391 )
1392 end
1393
1394 def fetch_activities_bounded(
1395 recipients,
1396 recipients_with_public,
1397 opts \\ %{},
1398 pagination \\ :keyset
1399 ) do
1400 fetch_activities_query([], opts)
1401 |> fetch_activities_bounded_query(recipients, recipients_with_public)
1402 |> Pagination.fetch_paginated(opts, pagination)
1403 |> Enum.reverse()
1404 end
1405
1406 @spec upload(Upload.source(), keyword()) :: {:ok, Object.t()} | {:error, any()}
1407 def upload(file, opts \\ []) do
1408 with {:ok, data} <- Upload.store(file, opts) do
1409 obj_data =
1410 if opts[:actor] do
1411 Map.put(data, "actor", opts[:actor])
1412 else
1413 data
1414 end
1415
1416 Repo.insert(%Object{data: obj_data})
1417 end
1418 end
1419
1420 @spec get_actor_url(any()) :: binary() | nil
1421 defp get_actor_url(url) when is_binary(url), do: url
1422 defp get_actor_url(%{"href" => href}) when is_binary(href), do: href
1423
1424 defp get_actor_url(url) when is_list(url) do
1425 url
1426 |> List.first()
1427 |> get_actor_url()
1428 end
1429
1430 defp get_actor_url(_url), do: nil
1431
1432 defp object_to_user_data(data) do
1433 avatar =
1434 data["icon"]["url"] &&
1435 %{
1436 "type" => "Image",
1437 "url" => [%{"href" => data["icon"]["url"]}]
1438 }
1439
1440 banner =
1441 data["image"]["url"] &&
1442 %{
1443 "type" => "Image",
1444 "url" => [%{"href" => data["image"]["url"]}]
1445 }
1446
1447 fields =
1448 data
1449 |> Map.get("attachment", [])
1450 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1451 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1452
1453 emojis =
1454 data
1455 |> Map.get("tag", [])
1456 |> Enum.filter(fn
1457 %{"type" => "Emoji"} -> true
1458 _ -> false
1459 end)
1460 |> Enum.reduce(%{}, fn %{"icon" => %{"url" => url}, "name" => name}, acc ->
1461 Map.put(acc, String.trim(name, ":"), url)
1462 end)
1463
1464 locked = data["manuallyApprovesFollowers"] || false
1465 data = Transmogrifier.maybe_fix_user_object(data)
1466 discoverable = data["discoverable"] || false
1467 invisible = data["invisible"] || false
1468 actor_type = data["type"] || "Person"
1469
1470 public_key =
1471 if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do
1472 data["publicKey"]["publicKeyPem"]
1473 else
1474 nil
1475 end
1476
1477 shared_inbox =
1478 if is_map(data["endpoints"]) && is_binary(data["endpoints"]["sharedInbox"]) do
1479 data["endpoints"]["sharedInbox"]
1480 else
1481 nil
1482 end
1483
1484 user_data = %{
1485 ap_id: data["id"],
1486 uri: get_actor_url(data["url"]),
1487 ap_enabled: true,
1488 banner: banner,
1489 fields: fields,
1490 emoji: emojis,
1491 locked: locked,
1492 discoverable: discoverable,
1493 invisible: invisible,
1494 avatar: avatar,
1495 name: data["name"],
1496 follower_address: data["followers"],
1497 following_address: data["following"],
1498 bio: data["summary"],
1499 actor_type: actor_type,
1500 also_known_as: Map.get(data, "alsoKnownAs", []),
1501 public_key: public_key,
1502 inbox: data["inbox"],
1503 shared_inbox: shared_inbox
1504 }
1505
1506 # nickname can be nil because of virtual actors
1507 user_data =
1508 if data["preferredUsername"] do
1509 Map.put(
1510 user_data,
1511 :nickname,
1512 "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
1513 )
1514 else
1515 Map.put(user_data, :nickname, nil)
1516 end
1517
1518 {:ok, user_data}
1519 end
1520
1521 def fetch_follow_information_for_user(user) do
1522 with {:ok, following_data} <-
1523 Fetcher.fetch_and_contain_remote_object_from_id(user.following_address),
1524 {:ok, hide_follows} <- collection_private(following_data),
1525 {:ok, followers_data} <-
1526 Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address),
1527 {:ok, hide_followers} <- collection_private(followers_data) do
1528 {:ok,
1529 %{
1530 hide_follows: hide_follows,
1531 follower_count: normalize_counter(followers_data["totalItems"]),
1532 following_count: normalize_counter(following_data["totalItems"]),
1533 hide_followers: hide_followers
1534 }}
1535 else
1536 {:error, _} = e -> e
1537 e -> {:error, e}
1538 end
1539 end
1540
1541 defp normalize_counter(counter) when is_integer(counter), do: counter
1542 defp normalize_counter(_), do: 0
1543
1544 def maybe_update_follow_information(user_data) do
1545 with {:enabled, true} <- {:enabled, Config.get([:instance, :external_user_synchronization])},
1546 {_, true} <- {:user_type_check, user_data[:type] in ["Person", "Service"]},
1547 {_, true} <-
1548 {:collections_available,
1549 !!(user_data[:following_address] && user_data[:follower_address])},
1550 {:ok, info} <-
1551 fetch_follow_information_for_user(user_data) do
1552 info = Map.merge(user_data[:info] || %{}, info)
1553
1554 user_data
1555 |> Map.put(:info, info)
1556 else
1557 {:user_type_check, false} ->
1558 user_data
1559
1560 {:collections_available, false} ->
1561 user_data
1562
1563 {:enabled, false} ->
1564 user_data
1565
1566 e ->
1567 Logger.error(
1568 "Follower/Following counter update for #{user_data.ap_id} failed.\n" <> inspect(e)
1569 )
1570
1571 user_data
1572 end
1573 end
1574
1575 defp collection_private(%{"first" => %{"type" => type}})
1576 when type in ["CollectionPage", "OrderedCollectionPage"],
1577 do: {:ok, false}
1578
1579 defp collection_private(%{"first" => first}) do
1580 with {:ok, %{"type" => type}} when type in ["CollectionPage", "OrderedCollectionPage"] <-
1581 Fetcher.fetch_and_contain_remote_object_from_id(first) do
1582 {:ok, false}
1583 else
1584 {:error, {:ok, %{status: code}}} when code in [401, 403] -> {:ok, true}
1585 {:error, _} = e -> e
1586 e -> {:error, e}
1587 end
1588 end
1589
1590 defp collection_private(_data), do: {:ok, true}
1591
1592 def user_data_from_user_object(data) do
1593 with {:ok, data} <- MRF.filter(data),
1594 {:ok, data} <- object_to_user_data(data) do
1595 {:ok, data}
1596 else
1597 e -> {:error, e}
1598 end
1599 end
1600
1601 def fetch_and_prepare_user_from_ap_id(ap_id) do
1602 with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
1603 {:ok, data} <- user_data_from_user_object(data),
1604 data <- maybe_update_follow_information(data) do
1605 {:ok, data}
1606 else
1607 {:error, "Object has been deleted"} = e ->
1608 Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
1609 {:error, e}
1610
1611 e ->
1612 Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
1613 {:error, e}
1614 end
1615 end
1616
1617 def make_user_from_ap_id(ap_id) do
1618 user = User.get_cached_by_ap_id(ap_id)
1619
1620 if user && !User.ap_enabled?(user) do
1621 Transmogrifier.upgrade_user_from_ap_id(ap_id)
1622 else
1623 with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
1624 if user do
1625 user
1626 |> User.remote_user_changeset(data)
1627 |> User.update_and_set_cache()
1628 else
1629 data
1630 |> User.remote_user_changeset()
1631 |> Repo.insert()
1632 |> User.set_cache()
1633 end
1634 else
1635 e -> {:error, e}
1636 end
1637 end
1638 end
1639
1640 def make_user_from_nickname(nickname) do
1641 with {:ok, %{"ap_id" => ap_id}} when not is_nil(ap_id) <- WebFinger.finger(nickname) do
1642 make_user_from_ap_id(ap_id)
1643 else
1644 _e -> {:error, "No AP id in WebFinger"}
1645 end
1646 end
1647
1648 # filter out broken threads
1649 def contain_broken_threads(%Activity{} = activity, %User{} = user) do
1650 entire_thread_visible_for_user?(activity, user)
1651 end
1652
1653 # do post-processing on a specific activity
1654 def contain_activity(%Activity{} = activity, %User{} = user) do
1655 contain_broken_threads(activity, user)
1656 end
1657
1658 def fetch_direct_messages_query do
1659 Activity
1660 |> restrict_type(%{"type" => "Create"})
1661 |> restrict_visibility(%{visibility: "direct"})
1662 |> order_by([activity], asc: activity.id)
1663 end
1664 end