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