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