Mastodon API: Add `exclude_visibilities` parameter to the timeline and notification...
[akkoma] / lib / pleroma / web / activity_pub / activity_pub.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 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.Conversation
10 alias Pleroma.Notification
11 alias Pleroma.Object
12 alias Pleroma.Object.Containment
13 alias Pleroma.Object.Fetcher
14 alias Pleroma.Pagination
15 alias Pleroma.Repo
16 alias Pleroma.Upload
17 alias Pleroma.User
18 alias Pleroma.Web.ActivityPub.MRF
19 alias Pleroma.Web.ActivityPub.Transmogrifier
20 alias Pleroma.Web.ActivityPub.Utils
21 alias Pleroma.Web.Streamer
22 alias Pleroma.Web.WebFinger
23 alias Pleroma.Workers.BackgroundWorker
24
25 import Ecto.Query
26 import Pleroma.Web.ActivityPub.Utils
27 import Pleroma.Web.ActivityPub.Visibility
28
29 require Logger
30 require Pleroma.Constants
31
32 # For Announce activities, we filter the recipients based on following status for any actors
33 # that match actual users. See issue #164 for more information about why this is necessary.
34 defp get_recipients(%{"type" => "Announce"} = data) do
35 to = Map.get(data, "to", [])
36 cc = Map.get(data, "cc", [])
37 bcc = Map.get(data, "bcc", [])
38 actor = User.get_cached_by_ap_id(data["actor"])
39
40 recipients =
41 Enum.filter(Enum.concat([to, cc, bcc]), fn recipient ->
42 case User.get_cached_by_ap_id(recipient) do
43 nil -> true
44 user -> User.following?(user, actor)
45 end
46 end)
47
48 {recipients, to, cc}
49 end
50
51 defp get_recipients(%{"type" => "Create"} = data) do
52 to = Map.get(data, "to", [])
53 cc = Map.get(data, "cc", [])
54 bcc = Map.get(data, "bcc", [])
55 actor = Map.get(data, "actor", [])
56 recipients = [to, cc, bcc, [actor]] |> Enum.concat() |> Enum.uniq()
57 {recipients, to, cc}
58 end
59
60 defp get_recipients(data) do
61 to = Map.get(data, "to", [])
62 cc = Map.get(data, "cc", [])
63 bcc = Map.get(data, "bcc", [])
64 recipients = Enum.concat([to, cc, bcc])
65 {recipients, to, cc}
66 end
67
68 defp check_actor_is_active(actor) do
69 if not is_nil(actor) do
70 with user <- User.get_cached_by_ap_id(actor),
71 false <- user.info.deactivated do
72 true
73 else
74 _e -> false
75 end
76 else
77 true
78 end
79 end
80
81 defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do
82 limit = Config.get([:instance, :remote_limit])
83 String.length(content) <= limit
84 end
85
86 defp check_remote_limit(_), do: true
87
88 def increase_note_count_if_public(actor, object) do
89 if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
90 end
91
92 def decrease_note_count_if_public(actor, object) do
93 if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
94 end
95
96 def increase_replies_count_if_reply(%{
97 "object" => %{"inReplyTo" => reply_ap_id} = object,
98 "type" => "Create"
99 }) do
100 if is_public?(object) do
101 Object.increase_replies_count(reply_ap_id)
102 end
103 end
104
105 def increase_replies_count_if_reply(_create_data), do: :noop
106
107 def decrease_replies_count_if_reply(%Object{
108 data: %{"inReplyTo" => reply_ap_id} = object
109 }) do
110 if is_public?(object) do
111 Object.decrease_replies_count(reply_ap_id)
112 end
113 end
114
115 def decrease_replies_count_if_reply(_object), do: :noop
116
117 def increase_poll_votes_if_vote(%{
118 "object" => %{"inReplyTo" => reply_ap_id, "name" => name},
119 "type" => "Create"
120 }) do
121 Object.increase_vote_count(reply_ap_id, name)
122 end
123
124 def increase_poll_votes_if_vote(_create_data), do: :noop
125
126 def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do
127 with nil <- Activity.normalize(map),
128 map <- lazy_put_activity_defaults(map, fake),
129 true <- bypass_actor_check || check_actor_is_active(map["actor"]),
130 {_, true} <- {:remote_limit_error, check_remote_limit(map)},
131 {:ok, map} <- MRF.filter(map),
132 {recipients, _, _} = get_recipients(map),
133 {:fake, false, map, recipients} <- {:fake, fake, map, recipients},
134 :ok <- Containment.contain_child(map),
135 {:ok, map, object} <- insert_full_object(map) do
136 {:ok, activity} =
137 Repo.insert(%Activity{
138 data: map,
139 local: local,
140 actor: map["actor"],
141 recipients: recipients
142 })
143
144 # Splice in the child object if we have one.
145 activity =
146 if not is_nil(object) do
147 Map.put(activity, :object, object)
148 else
149 activity
150 end
151
152 BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
153
154 Notification.create_notifications(activity)
155
156 participations =
157 activity
158 |> Conversation.create_or_bump_for()
159 |> get_participations()
160
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 get_participations({:ok, %{participations: participations}}), do: participations
186 defp get_participations(_), do: []
187
188 def stream_out_participations(participations) do
189 participations =
190 participations
191 |> Repo.preload(:user)
192
193 Streamer.stream("participation", participations)
194 end
195
196 def stream_out_participations(%Object{data: %{"context" => context}}, user) do
197 with %Conversation{} = conversation <- Conversation.get_for_ap_id(context),
198 conversation = Repo.preload(conversation, :participations),
199 last_activity_id =
200 fetch_latest_activity_id_for_context(conversation.ap_id, %{
201 "user" => user,
202 "blocking_user" => user
203 }) do
204 if last_activity_id do
205 stream_out_participations(conversation.participations)
206 end
207 end
208 end
209
210 def stream_out_participations(_, _), do: :noop
211
212 def stream_out(%Activity{data: %{"type" => data_type}} = activity)
213 when data_type in ["Create", "Announce", "Delete"] do
214 activity
215 |> Topics.get_activity_topics()
216 |> Streamer.stream(activity)
217 end
218
219 def stream_out(_activity) do
220 :noop
221 end
222
223 def create(%{to: to, actor: actor, context: context, object: object} = params, fake \\ false) do
224 additional = params[:additional] || %{}
225 # only accept false as false value
226 local = !(params[:local] == false)
227 published = params[:published]
228
229 with create_data <-
230 make_create_data(
231 %{to: to, actor: actor, published: published, context: context, object: object},
232 additional
233 ),
234 {:ok, activity} <- insert(create_data, local, fake),
235 {:fake, false, activity} <- {:fake, fake, activity},
236 _ <- increase_replies_count_if_reply(create_data),
237 _ <- increase_poll_votes_if_vote(create_data),
238 # Changing note count prior to enqueuing federation task in order to avoid
239 # race conditions on updating user.info
240 {:ok, _actor} <- increase_note_count_if_public(actor, activity),
241 :ok <- maybe_federate(activity) do
242 {:ok, activity}
243 else
244 {:fake, true, activity} ->
245 {:ok, activity}
246
247 {:error, message} ->
248 {:error, message}
249 end
250 end
251
252 def listen(%{to: to, actor: actor, context: context, object: object} = params) do
253 additional = params[:additional] || %{}
254 # only accept false as false value
255 local = !(params[:local] == false)
256 published = params[:published]
257
258 with listen_data <-
259 make_listen_data(
260 %{to: to, actor: actor, published: published, context: context, object: object},
261 additional
262 ),
263 {:ok, activity} <- insert(listen_data, local),
264 :ok <- maybe_federate(activity) do
265 {:ok, activity}
266 else
267 {:error, message} ->
268 {:error, message}
269 end
270 end
271
272 def accept(params) do
273 accept_or_reject("Accept", params)
274 end
275
276 def reject(params) do
277 accept_or_reject("Reject", params)
278 end
279
280 def accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do
281 local = Map.get(params, :local, true)
282 activity_id = Map.get(params, :activity_id, nil)
283
284 with data <-
285 %{"to" => to, "type" => type, "actor" => actor.ap_id, "object" => object}
286 |> Utils.maybe_put("id", activity_id),
287 {:ok, activity} <- insert(data, local),
288 :ok <- maybe_federate(activity) do
289 {:ok, activity}
290 end
291 end
292
293 def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
294 local = !(params[:local] == false)
295 activity_id = params[:activity_id]
296
297 with data <- %{
298 "to" => to,
299 "cc" => cc,
300 "type" => "Update",
301 "actor" => actor,
302 "object" => object
303 },
304 data <- Utils.maybe_put(data, "id", activity_id),
305 {:ok, activity} <- insert(data, local),
306 :ok <- maybe_federate(activity) do
307 {:ok, activity}
308 end
309 end
310
311 # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
312 def like(
313 %User{ap_id: ap_id} = user,
314 %Object{data: %{"id" => _}} = object,
315 activity_id \\ nil,
316 local \\ true
317 ) do
318 with nil <- get_existing_like(ap_id, object),
319 like_data <- make_like_data(user, object, activity_id),
320 {:ok, activity} <- insert(like_data, local),
321 {:ok, object} <- add_like_to_object(activity, object),
322 :ok <- maybe_federate(activity) do
323 {:ok, activity, object}
324 else
325 %Activity{} = activity -> {:ok, activity, object}
326 error -> {:error, error}
327 end
328 end
329
330 def unlike(%User{} = actor, %Object{} = object, activity_id \\ nil, local \\ true) do
331 with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),
332 unlike_data <- make_unlike_data(actor, like_activity, activity_id),
333 {:ok, unlike_activity} <- insert(unlike_data, local),
334 {:ok, _activity} <- Repo.delete(like_activity),
335 {:ok, object} <- remove_like_from_object(like_activity, object),
336 :ok <- maybe_federate(unlike_activity) do
337 {:ok, unlike_activity, like_activity, object}
338 else
339 _e -> {:ok, object}
340 end
341 end
342
343 def announce(
344 %User{ap_id: _} = user,
345 %Object{data: %{"id" => _}} = object,
346 activity_id \\ nil,
347 local \\ true,
348 public \\ true
349 ) do
350 with true <- is_announceable?(object, user, public),
351 announce_data <- make_announce_data(user, object, activity_id, public),
352 {:ok, activity} <- insert(announce_data, local),
353 {:ok, object} <- add_announce_to_object(activity, object),
354 :ok <- maybe_federate(activity) do
355 {:ok, activity, object}
356 else
357 error -> {:error, error}
358 end
359 end
360
361 def unannounce(
362 %User{} = actor,
363 %Object{} = object,
364 activity_id \\ nil,
365 local \\ true
366 ) do
367 with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
368 unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
369 {:ok, unannounce_activity} <- insert(unannounce_data, local),
370 :ok <- maybe_federate(unannounce_activity),
371 {:ok, _activity} <- Repo.delete(announce_activity),
372 {:ok, object} <- remove_announce_from_object(announce_activity, object) do
373 {:ok, unannounce_activity, object}
374 else
375 _e -> {:ok, object}
376 end
377 end
378
379 def follow(follower, followed, activity_id \\ nil, local \\ true) do
380 with data <- make_follow_data(follower, followed, activity_id),
381 {:ok, activity} <- insert(data, local),
382 :ok <- maybe_federate(activity),
383 _ <- User.set_follow_state_cache(follower.ap_id, followed.ap_id, activity.data["state"]) do
384 {:ok, activity}
385 end
386 end
387
388 def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
389 with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
390 {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
391 unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
392 {:ok, activity} <- insert(unfollow_data, local),
393 :ok <- maybe_federate(activity) do
394 {:ok, activity}
395 end
396 end
397
398 def delete(%User{ap_id: ap_id, follower_address: follower_address} = user) do
399 with data <- %{
400 "to" => [follower_address],
401 "type" => "Delete",
402 "actor" => ap_id,
403 "object" => %{"type" => "Person", "id" => ap_id}
404 },
405 {:ok, activity} <- insert(data, true, true, true),
406 :ok <- maybe_federate(activity) do
407 {:ok, user}
408 end
409 end
410
411 def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, options \\ []) do
412 local = Keyword.get(options, :local, true)
413 activity_id = Keyword.get(options, :activity_id, nil)
414 actor = Keyword.get(options, :actor, actor)
415
416 user = User.get_cached_by_ap_id(actor)
417 to = (object.data["to"] || []) ++ (object.data["cc"] || [])
418
419 with {:ok, object, activity} <- Object.delete(object),
420 data <-
421 %{
422 "type" => "Delete",
423 "actor" => actor,
424 "object" => id,
425 "to" => to,
426 "deleted_activity_id" => activity && activity.id
427 }
428 |> maybe_put("id", activity_id),
429 {:ok, activity} <- insert(data, local, false),
430 stream_out_participations(object, user),
431 _ <- decrease_replies_count_if_reply(object),
432 # Changing note count prior to enqueuing federation task in order to avoid
433 # race conditions on updating user.info
434 {:ok, _actor} <- decrease_note_count_if_public(user, object),
435 :ok <- maybe_federate(activity) do
436 {:ok, activity}
437 end
438 end
439
440 @spec block(User.t(), User.t(), String.t() | nil, boolean) :: {:ok, Activity.t() | nil}
441 def block(blocker, blocked, activity_id \\ nil, local \\ true) do
442 outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
443 unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
444
445 if unfollow_blocked do
446 follow_activity = fetch_latest_follow(blocker, blocked)
447 if follow_activity, do: unfollow(blocker, blocked, nil, local)
448 end
449
450 with true <- outgoing_blocks,
451 block_data <- make_block_data(blocker, blocked, activity_id),
452 {:ok, activity} <- insert(block_data, local),
453 :ok <- maybe_federate(activity) do
454 {:ok, activity}
455 else
456 _e -> {:ok, nil}
457 end
458 end
459
460 def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
461 with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
462 unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
463 {:ok, activity} <- insert(unblock_data, local),
464 :ok <- maybe_federate(activity) do
465 {:ok, activity}
466 end
467 end
468
469 @spec flag(map()) :: {:ok, Activity.t()} | any
470 def flag(
471 %{
472 actor: actor,
473 context: _context,
474 account: account,
475 statuses: statuses,
476 content: content
477 } = params
478 ) do
479 # only accept false as false value
480 local = !(params[:local] == false)
481 forward = !(params[:forward] == false)
482
483 additional = params[:additional] || %{}
484
485 additional =
486 if forward do
487 Map.merge(additional, %{"to" => [], "cc" => [account.ap_id]})
488 else
489 Map.merge(additional, %{"to" => [], "cc" => []})
490 end
491
492 with flag_data <- make_flag_data(params, additional),
493 {:ok, activity} <- insert(flag_data, local),
494 :ok <- maybe_federate(activity) do
495 Enum.each(User.all_superusers(), fn superuser ->
496 superuser
497 |> Pleroma.Emails.AdminEmail.report(actor, account, statuses, content)
498 |> Pleroma.Emails.Mailer.deliver_async()
499 end)
500
501 {:ok, activity}
502 end
503 end
504
505 defp fetch_activities_for_context_query(context, opts) do
506 public = [Pleroma.Constants.as_public()]
507
508 recipients =
509 if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
510
511 from(activity in Activity)
512 |> maybe_preload_objects(opts)
513 |> maybe_preload_bookmarks(opts)
514 |> maybe_set_thread_muted_field(opts)
515 |> restrict_blocked(opts)
516 |> restrict_recipients(recipients, opts["user"])
517 |> where(
518 [activity],
519 fragment(
520 "?->>'type' = ? and ?->>'context' = ?",
521 activity.data,
522 "Create",
523 activity.data,
524 ^context
525 )
526 )
527 |> exclude_poll_votes(opts)
528 |> exclude_id(opts)
529 |> order_by([activity], desc: activity.id)
530 end
531
532 @spec fetch_activities_for_context(String.t(), keyword() | map()) :: [Activity.t()]
533 def fetch_activities_for_context(context, opts \\ %{}) do
534 context
535 |> fetch_activities_for_context_query(opts)
536 |> Repo.all()
537 end
538
539 @spec fetch_latest_activity_id_for_context(String.t(), keyword() | map()) ::
540 FlakeId.Ecto.CompatType.t() | nil
541 def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
542 context
543 |> fetch_activities_for_context_query(Map.merge(%{"skip_preload" => true}, opts))
544 |> limit(1)
545 |> select([a], a.id)
546 |> Repo.one()
547 end
548
549 def fetch_public_activities(opts \\ %{}, pagination \\ :keyset) do
550 opts = Map.drop(opts, ["user"])
551
552 [Pleroma.Constants.as_public()]
553 |> fetch_activities_query(opts)
554 |> restrict_unlisted()
555 |> Pagination.fetch_paginated(opts, pagination)
556 |> Enum.reverse()
557 end
558
559 @valid_visibilities ~w[direct unlisted public private]
560
561 defp restrict_visibility(query, %{visibility: visibility})
562 when is_list(visibility) do
563 if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
564 query =
565 from(
566 a in query,
567 where:
568 fragment(
569 "activity_visibility(?, ?, ?) = ANY (?)",
570 a.actor,
571 a.recipients,
572 a.data,
573 ^visibility
574 )
575 )
576
577 query
578 else
579 Logger.error("Could not restrict visibility to #{visibility}")
580 end
581 end
582
583 defp restrict_visibility(query, %{visibility: visibility})
584 when visibility in @valid_visibilities do
585 from(
586 a in query,
587 where:
588 fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
589 )
590 end
591
592 defp restrict_visibility(_query, %{visibility: visibility})
593 when visibility not in @valid_visibilities do
594 Logger.error("Could not restrict visibility to #{visibility}")
595 end
596
597 defp restrict_visibility(query, _visibility), do: query
598
599 defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
600 when is_list(visibility) do
601 if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
602 from(
603 a in query,
604 where:
605 not fragment(
606 "activity_visibility(?, ?, ?) = ANY (?)",
607 a.actor,
608 a.recipients,
609 a.data,
610 ^visibility
611 )
612 )
613 else
614 Logger.error("Could not exclude visibility to #{visibility}")
615 query
616 end
617 end
618
619 defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
620 when visibility in @valid_visibilities do
621 from(
622 a in query,
623 where:
624 not fragment(
625 "activity_visibility(?, ?, ?) = ?",
626 a.actor,
627 a.recipients,
628 a.data,
629 ^visibility
630 )
631 )
632 end
633
634 defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
635 when visibility not in @valid_visibilities do
636 Logger.error("Could not exclude visibility to #{visibility}")
637 query
638 end
639
640 defp exclude_visibility(query, _visibility), do: query
641
642 defp restrict_thread_visibility(query, _, %{skip_thread_containment: true} = _),
643 do: query
644
645 defp restrict_thread_visibility(
646 query,
647 %{"user" => %User{info: %{skip_thread_containment: true}}},
648 _
649 ),
650 do: query
651
652 defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}, _) do
653 from(
654 a in query,
655 where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
656 )
657 end
658
659 defp restrict_thread_visibility(query, _, _), do: query
660
661 def fetch_user_abstract_activities(user, reading_user, params \\ %{}) do
662 params =
663 params
664 |> Map.put("user", reading_user)
665 |> Map.put("actor_id", user.ap_id)
666 |> Map.put("whole_db", true)
667
668 recipients =
669 user_activities_recipients(%{
670 "godmode" => params["godmode"],
671 "reading_user" => reading_user
672 })
673
674 fetch_activities(recipients, params)
675 |> Enum.reverse()
676 end
677
678 def fetch_user_activities(user, reading_user, params \\ %{}) do
679 params =
680 params
681 |> Map.put("type", ["Create", "Announce"])
682 |> Map.put("user", reading_user)
683 |> Map.put("actor_id", user.ap_id)
684 |> Map.put("whole_db", true)
685 |> Map.put("pinned_activity_ids", user.info.pinned_activities)
686
687 recipients =
688 user_activities_recipients(%{
689 "godmode" => params["godmode"],
690 "reading_user" => reading_user
691 })
692
693 fetch_activities(recipients, params)
694 |> Enum.reverse()
695 end
696
697 defp user_activities_recipients(%{"godmode" => true}) do
698 []
699 end
700
701 defp user_activities_recipients(%{"reading_user" => reading_user}) do
702 if reading_user do
703 [Pleroma.Constants.as_public()] ++ [reading_user.ap_id | reading_user.following]
704 else
705 [Pleroma.Constants.as_public()]
706 end
707 end
708
709 defp restrict_since(query, %{"since_id" => ""}), do: query
710
711 defp restrict_since(query, %{"since_id" => since_id}) do
712 from(activity in query, where: activity.id > ^since_id)
713 end
714
715 defp restrict_since(query, _), do: query
716
717 defp restrict_tag_reject(_query, %{"tag_reject" => _tag_reject, "skip_preload" => true}) do
718 raise "Can't use the child object without preloading!"
719 end
720
721 defp restrict_tag_reject(query, %{"tag_reject" => tag_reject})
722 when is_list(tag_reject) and tag_reject != [] do
723 from(
724 [_activity, object] in query,
725 where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
726 )
727 end
728
729 defp restrict_tag_reject(query, _), do: query
730
731 defp restrict_tag_all(_query, %{"tag_all" => _tag_all, "skip_preload" => true}) do
732 raise "Can't use the child object without preloading!"
733 end
734
735 defp restrict_tag_all(query, %{"tag_all" => tag_all})
736 when is_list(tag_all) and tag_all != [] do
737 from(
738 [_activity, object] in query,
739 where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
740 )
741 end
742
743 defp restrict_tag_all(query, _), do: query
744
745 defp restrict_tag(_query, %{"tag" => _tag, "skip_preload" => true}) do
746 raise "Can't use the child object without preloading!"
747 end
748
749 defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do
750 from(
751 [_activity, object] in query,
752 where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag)
753 )
754 end
755
756 defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do
757 from(
758 [_activity, object] in query,
759 where: fragment("(?)->'tag' \\? (?)", object.data, ^tag)
760 )
761 end
762
763 defp restrict_tag(query, _), do: query
764
765 defp restrict_recipients(query, [], _user), do: query
766
767 defp restrict_recipients(query, recipients, nil) do
768 from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
769 end
770
771 defp restrict_recipients(query, recipients, user) do
772 from(
773 activity in query,
774 where: fragment("? && ?", ^recipients, activity.recipients),
775 or_where: activity.actor == ^user.ap_id
776 )
777 end
778
779 defp restrict_local(query, %{"local_only" => true}) do
780 from(activity in query, where: activity.local == true)
781 end
782
783 defp restrict_local(query, _), do: query
784
785 defp restrict_actor(query, %{"actor_id" => actor_id}) do
786 from(activity in query, where: activity.actor == ^actor_id)
787 end
788
789 defp restrict_actor(query, _), do: query
790
791 defp restrict_type(query, %{"type" => type}) when is_binary(type) do
792 from(activity in query, where: fragment("?->>'type' = ?", activity.data, ^type))
793 end
794
795 defp restrict_type(query, %{"type" => type}) do
796 from(activity in query, where: fragment("?->>'type' = ANY(?)", activity.data, ^type))
797 end
798
799 defp restrict_type(query, _), do: query
800
801 defp restrict_state(query, %{"state" => state}) do
802 from(activity in query, where: fragment("?->>'state' = ?", activity.data, ^state))
803 end
804
805 defp restrict_state(query, _), do: query
806
807 defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
808 from(
809 [_activity, object] in query,
810 where: fragment("(?)->'likes' \\? (?)", object.data, ^ap_id)
811 )
812 end
813
814 defp restrict_favorited_by(query, _), do: query
815
816 defp restrict_media(_query, %{"only_media" => _val, "skip_preload" => true}) do
817 raise "Can't use the child object without preloading!"
818 end
819
820 defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
821 from(
822 [_activity, object] in query,
823 where: fragment("not (?)->'attachment' = (?)", object.data, ^[])
824 )
825 end
826
827 defp restrict_media(query, _), do: query
828
829 defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do
830 from(
831 [_activity, object] in query,
832 where: fragment("?->>'inReplyTo' is null", object.data)
833 )
834 end
835
836 defp restrict_replies(query, _), do: query
837
838 defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val == "true" or val == "1" do
839 from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
840 end
841
842 defp restrict_reblogs(query, _), do: query
843
844 defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
845
846 defp restrict_muted(query, %{"muting_user" => %User{info: info}} = opts) do
847 mutes = info.mutes
848
849 query =
850 from([activity] in query,
851 where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
852 where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
853 )
854
855 unless opts["skip_preload"] do
856 from([thread_mute: tm] in query, where: is_nil(tm.user_id))
857 else
858 query
859 end
860 end
861
862 defp restrict_muted(query, _), do: query
863
864 defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
865 blocks = info.blocks || []
866 domain_blocks = info.domain_blocks || []
867
868 query =
869 if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query)
870
871 from(
872 [activity, object: o] in query,
873 where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
874 where: fragment("not (? && ?)", activity.recipients, ^blocks),
875 where:
876 fragment(
877 "not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
878 activity.data,
879 activity.data,
880 ^blocks
881 ),
882 where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks),
883 where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks)
884 )
885 end
886
887 defp restrict_blocked(query, _), do: query
888
889 defp restrict_unlisted(query) do
890 from(
891 activity in query,
892 where:
893 fragment(
894 "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
895 activity.data,
896 ^[Pleroma.Constants.as_public()]
897 )
898 )
899 end
900
901 defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids}) do
902 from(activity in query, where: activity.id in ^ids)
903 end
904
905 defp restrict_pinned(query, _), do: query
906
907 defp restrict_muted_reblogs(query, %{"muting_user" => %User{info: info}}) do
908 muted_reblogs = info.muted_reblogs || []
909
910 from(
911 activity in query,
912 where:
913 fragment(
914 "not ( ?->>'type' = 'Announce' and ? = ANY(?))",
915 activity.data,
916 activity.actor,
917 ^muted_reblogs
918 )
919 )
920 end
921
922 defp restrict_muted_reblogs(query, _), do: query
923
924 defp exclude_poll_votes(query, %{"include_poll_votes" => true}), do: query
925
926 defp exclude_poll_votes(query, _) do
927 if has_named_binding?(query, :object) do
928 from([activity, object: o] in query,
929 where: fragment("not(?->>'type' = ?)", o.data, "Answer")
930 )
931 else
932 query
933 end
934 end
935
936 defp exclude_id(query, %{"exclude_id" => id}) when is_binary(id) do
937 from(activity in query, where: activity.id != ^id)
938 end
939
940 defp exclude_id(query, _), do: query
941
942 defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
943
944 defp maybe_preload_objects(query, _) do
945 query
946 |> Activity.with_preloaded_object()
947 end
948
949 defp maybe_preload_bookmarks(query, %{"skip_preload" => true}), do: query
950
951 defp maybe_preload_bookmarks(query, opts) do
952 query
953 |> Activity.with_preloaded_bookmark(opts["user"])
954 end
955
956 defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query
957
958 defp maybe_set_thread_muted_field(query, opts) do
959 query
960 |> Activity.with_set_thread_muted_field(opts["muting_user"] || opts["user"])
961 end
962
963 defp maybe_order(query, %{order: :desc}) do
964 query
965 |> order_by(desc: :id)
966 end
967
968 defp maybe_order(query, %{order: :asc}) do
969 query
970 |> order_by(asc: :id)
971 end
972
973 defp maybe_order(query, _), do: query
974
975 def fetch_activities_query(recipients, opts \\ %{}) do
976 config = %{
977 skip_thread_containment: Config.get([:instance, :skip_thread_containment])
978 }
979
980 Activity
981 |> maybe_preload_objects(opts)
982 |> maybe_preload_bookmarks(opts)
983 |> maybe_set_thread_muted_field(opts)
984 |> maybe_order(opts)
985 |> restrict_recipients(recipients, opts["user"])
986 |> restrict_tag(opts)
987 |> restrict_tag_reject(opts)
988 |> restrict_tag_all(opts)
989 |> restrict_since(opts)
990 |> restrict_local(opts)
991 |> restrict_actor(opts)
992 |> restrict_type(opts)
993 |> restrict_state(opts)
994 |> restrict_favorited_by(opts)
995 |> restrict_blocked(opts)
996 |> restrict_muted(opts)
997 |> restrict_media(opts)
998 |> restrict_visibility(opts)
999 |> restrict_thread_visibility(opts, config)
1000 |> restrict_replies(opts)
1001 |> restrict_reblogs(opts)
1002 |> restrict_pinned(opts)
1003 |> restrict_muted_reblogs(opts)
1004 |> Activity.restrict_deactivated_users()
1005 |> exclude_poll_votes(opts)
1006 |> exclude_visibility(opts)
1007 end
1008
1009 def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
1010 list_memberships = Pleroma.List.memberships(opts["user"])
1011
1012 fetch_activities_query(recipients ++ list_memberships, opts)
1013 |> Pagination.fetch_paginated(opts, pagination)
1014 |> Enum.reverse()
1015 |> maybe_update_cc(list_memberships, opts["user"])
1016 end
1017
1018 defp maybe_update_cc(activities, list_memberships, %User{ap_id: user_ap_id})
1019 when is_list(list_memberships) and length(list_memberships) > 0 do
1020 Enum.map(activities, fn
1021 %{data: %{"bcc" => bcc}} = activity when is_list(bcc) and length(bcc) > 0 ->
1022 if Enum.any?(bcc, &(&1 in list_memberships)) do
1023 update_in(activity.data["cc"], &[user_ap_id | &1])
1024 else
1025 activity
1026 end
1027
1028 activity ->
1029 activity
1030 end)
1031 end
1032
1033 defp maybe_update_cc(activities, _, _), do: activities
1034
1035 def fetch_activities_bounded_query(query, recipients, recipients_with_public) do
1036 from(activity in query,
1037 where:
1038 fragment("? && ?", activity.recipients, ^recipients) or
1039 (fragment("? && ?", activity.recipients, ^recipients_with_public) and
1040 ^Pleroma.Constants.as_public() in activity.recipients)
1041 )
1042 end
1043
1044 def fetch_activities_bounded(
1045 recipients,
1046 recipients_with_public,
1047 opts \\ %{},
1048 pagination \\ :keyset
1049 ) do
1050 fetch_activities_query([], opts)
1051 |> fetch_activities_bounded_query(recipients, recipients_with_public)
1052 |> Pagination.fetch_paginated(opts, pagination)
1053 |> Enum.reverse()
1054 end
1055
1056 def upload(file, opts \\ []) do
1057 with {:ok, data} <- Upload.store(file, opts) do
1058 obj_data =
1059 if opts[:actor] do
1060 Map.put(data, "actor", opts[:actor])
1061 else
1062 data
1063 end
1064
1065 Repo.insert(%Object{data: obj_data})
1066 end
1067 end
1068
1069 defp object_to_user_data(data) do
1070 avatar =
1071 data["icon"]["url"] &&
1072 %{
1073 "type" => "Image",
1074 "url" => [%{"href" => data["icon"]["url"]}]
1075 }
1076
1077 banner =
1078 data["image"]["url"] &&
1079 %{
1080 "type" => "Image",
1081 "url" => [%{"href" => data["image"]["url"]}]
1082 }
1083
1084 fields =
1085 data
1086 |> Map.get("attachment", [])
1087 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1088 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1089
1090 locked = data["manuallyApprovesFollowers"] || false
1091 data = Transmogrifier.maybe_fix_user_object(data)
1092 discoverable = data["discoverable"] || false
1093
1094 user_data = %{
1095 ap_id: data["id"],
1096 info: %{
1097 ap_enabled: true,
1098 source_data: data,
1099 banner: banner,
1100 fields: fields,
1101 locked: locked,
1102 discoverable: discoverable
1103 },
1104 avatar: avatar,
1105 name: data["name"],
1106 follower_address: data["followers"],
1107 following_address: data["following"],
1108 bio: data["summary"]
1109 }
1110
1111 # nickname can be nil because of virtual actors
1112 user_data =
1113 if data["preferredUsername"] do
1114 Map.put(
1115 user_data,
1116 :nickname,
1117 "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
1118 )
1119 else
1120 Map.put(user_data, :nickname, nil)
1121 end
1122
1123 {:ok, user_data}
1124 end
1125
1126 def fetch_follow_information_for_user(user) do
1127 with {:ok, following_data} <-
1128 Fetcher.fetch_and_contain_remote_object_from_id(user.following_address),
1129 following_count when is_integer(following_count) <- following_data["totalItems"],
1130 {:ok, hide_follows} <- collection_private(following_data),
1131 {:ok, followers_data} <-
1132 Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address),
1133 followers_count when is_integer(followers_count) <- followers_data["totalItems"],
1134 {:ok, hide_followers} <- collection_private(followers_data) do
1135 {:ok,
1136 %{
1137 hide_follows: hide_follows,
1138 follower_count: followers_count,
1139 following_count: following_count,
1140 hide_followers: hide_followers
1141 }}
1142 else
1143 {:error, _} = e ->
1144 e
1145
1146 e ->
1147 {:error, e}
1148 end
1149 end
1150
1151 defp maybe_update_follow_information(data) do
1152 with {:enabled, true} <-
1153 {:enabled, Pleroma.Config.get([:instance, :external_user_synchronization])},
1154 {:ok, info} <- fetch_follow_information_for_user(data) do
1155 info = Map.merge(data.info, info)
1156 Map.put(data, :info, info)
1157 else
1158 {:enabled, false} ->
1159 data
1160
1161 e ->
1162 Logger.error(
1163 "Follower/Following counter update for #{data.ap_id} failed.\n" <> inspect(e)
1164 )
1165
1166 data
1167 end
1168 end
1169
1170 defp collection_private(data) do
1171 if is_map(data["first"]) and
1172 data["first"]["type"] in ["CollectionPage", "OrderedCollectionPage"] do
1173 {:ok, false}
1174 else
1175 with {:ok, %{"type" => type}} when type in ["CollectionPage", "OrderedCollectionPage"] <-
1176 Fetcher.fetch_and_contain_remote_object_from_id(data["first"]) do
1177 {:ok, false}
1178 else
1179 {:error, {:ok, %{status: code}}} when code in [401, 403] ->
1180 {:ok, true}
1181
1182 {:error, _} = e ->
1183 e
1184
1185 e ->
1186 {:error, e}
1187 end
1188 end
1189 end
1190
1191 def user_data_from_user_object(data) do
1192 with {:ok, data} <- MRF.filter(data),
1193 {:ok, data} <- object_to_user_data(data) do
1194 {:ok, data}
1195 else
1196 e -> {:error, e}
1197 end
1198 end
1199
1200 def fetch_and_prepare_user_from_ap_id(ap_id) do
1201 with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
1202 {:ok, data} <- user_data_from_user_object(data),
1203 data <- maybe_update_follow_information(data) do
1204 {:ok, data}
1205 else
1206 e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
1207 end
1208 end
1209
1210 def make_user_from_ap_id(ap_id) do
1211 if _user = User.get_cached_by_ap_id(ap_id) do
1212 Transmogrifier.upgrade_user_from_ap_id(ap_id)
1213 else
1214 with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
1215 User.insert_or_update_user(data)
1216 else
1217 e -> {:error, e}
1218 end
1219 end
1220 end
1221
1222 def make_user_from_nickname(nickname) do
1223 with {:ok, %{"ap_id" => ap_id}} when not is_nil(ap_id) <- WebFinger.finger(nickname) do
1224 make_user_from_ap_id(ap_id)
1225 else
1226 _e -> {:error, "No AP id in WebFinger"}
1227 end
1228 end
1229
1230 # filter out broken threads
1231 def contain_broken_threads(%Activity{} = activity, %User{} = user) do
1232 entire_thread_visible_for_user?(activity, user)
1233 end
1234
1235 # do post-processing on a specific activity
1236 def contain_activity(%Activity{} = activity, %User{} = user) do
1237 contain_broken_threads(activity, user)
1238 end
1239
1240 def fetch_direct_messages_query do
1241 Activity
1242 |> restrict_type(%{"type" => "Create"})
1243 |> restrict_visibility(%{visibility: "direct"})
1244 |> order_by([activity], asc: activity.id)
1245 end
1246 end