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