07652fae41aee38f156e81624edbc4e719f451b6
[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 true
72 else
73 _e -> false
74 end
75 else
76 true
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, bypass_actor_check \\ false) when is_map(map) do
126 with nil <- Activity.normalize(map),
127 map <- lazy_put_activity_defaults(map, fake),
128 true <- bypass_actor_check || 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),
397 _ <- User.set_follow_state_cache(follower.ap_id, followed.ap_id, activity.data["state"]) do
398 {:ok, activity}
399 end
400 end
401
402 def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
403 with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
404 {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
405 unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
406 {:ok, activity} <- insert(unfollow_data, local),
407 :ok <- maybe_federate(activity) do
408 {:ok, activity}
409 end
410 end
411
412 def delete(%User{ap_id: ap_id, follower_address: follower_address} = user) do
413 with data <- %{
414 "to" => [follower_address],
415 "type" => "Delete",
416 "actor" => ap_id,
417 "object" => %{"type" => "Person", "id" => ap_id}
418 },
419 {:ok, activity} <- insert(data, true, true, true),
420 :ok <- maybe_federate(activity) do
421 {:ok, user}
422 end
423 end
424
425 def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
426 user = User.get_cached_by_ap_id(actor)
427 to = (object.data["to"] || []) ++ (object.data["cc"] || [])
428
429 with {:ok, object, activity} <- Object.delete(object),
430 data <- %{
431 "type" => "Delete",
432 "actor" => actor,
433 "object" => id,
434 "to" => to,
435 "deleted_activity_id" => activity && activity.id
436 },
437 {:ok, activity} <- insert(data, local, false),
438 stream_out_participations(object, user),
439 _ <- decrease_replies_count_if_reply(object),
440 # Changing note count prior to enqueuing federation task in order to avoid
441 # race conditions on updating user.info
442 {:ok, _actor} <- decrease_note_count_if_public(user, object),
443 :ok <- maybe_federate(activity) do
444 {:ok, activity}
445 end
446 end
447
448 def block(blocker, blocked, activity_id \\ nil, local \\ true) do
449 outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
450 unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
451
452 if unfollow_blocked do
453 follow_activity = fetch_latest_follow(blocker, blocked)
454 if follow_activity, do: unfollow(blocker, blocked, nil, local)
455 end
456
457 with true <- outgoing_blocks,
458 block_data <- make_block_data(blocker, blocked, activity_id),
459 {:ok, activity} <- insert(block_data, local),
460 :ok <- maybe_federate(activity) do
461 {:ok, activity}
462 else
463 _e -> {:ok, nil}
464 end
465 end
466
467 def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
468 with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
469 unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
470 {:ok, activity} <- insert(unblock_data, local),
471 :ok <- maybe_federate(activity) do
472 {:ok, activity}
473 end
474 end
475
476 def flag(
477 %{
478 actor: actor,
479 context: context,
480 account: account,
481 statuses: statuses,
482 content: content
483 } = params
484 ) do
485 # only accept false as false value
486 local = !(params[:local] == false)
487 forward = !(params[:forward] == false)
488
489 additional = params[:additional] || %{}
490
491 params = %{
492 actor: actor,
493 context: context,
494 account: account,
495 statuses: statuses,
496 content: content
497 }
498
499 additional =
500 if forward do
501 Map.merge(additional, %{"to" => [], "cc" => [account.ap_id]})
502 else
503 Map.merge(additional, %{"to" => [], "cc" => []})
504 end
505
506 with flag_data <- make_flag_data(params, additional),
507 {:ok, activity} <- insert(flag_data, local),
508 :ok <- maybe_federate(activity) do
509 Enum.each(User.all_superusers(), fn superuser ->
510 superuser
511 |> Pleroma.Emails.AdminEmail.report(actor, account, statuses, content)
512 |> Pleroma.Emails.Mailer.deliver_async()
513 end)
514
515 {:ok, activity}
516 end
517 end
518
519 defp fetch_activities_for_context_query(context, opts) do
520 public = [Pleroma.Constants.as_public()]
521
522 recipients =
523 if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
524
525 from(activity in Activity)
526 |> maybe_preload_objects(opts)
527 |> maybe_preload_bookmarks(opts)
528 |> maybe_set_thread_muted_field(opts)
529 |> restrict_blocked(opts)
530 |> restrict_recipients(recipients, opts["user"])
531 |> where(
532 [activity],
533 fragment(
534 "?->>'type' = ? and ?->>'context' = ?",
535 activity.data,
536 "Create",
537 activity.data,
538 ^context
539 )
540 )
541 |> exclude_poll_votes(opts)
542 |> exclude_id(opts)
543 |> order_by([activity], desc: activity.id)
544 end
545
546 @spec fetch_activities_for_context(String.t(), keyword() | map()) :: [Activity.t()]
547 def fetch_activities_for_context(context, opts \\ %{}) do
548 context
549 |> fetch_activities_for_context_query(opts)
550 |> Repo.all()
551 end
552
553 @spec fetch_latest_activity_id_for_context(String.t(), keyword() | map()) ::
554 Pleroma.FlakeId.t() | nil
555 def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
556 context
557 |> fetch_activities_for_context_query(Map.merge(%{"skip_preload" => true}, opts))
558 |> limit(1)
559 |> select([a], a.id)
560 |> Repo.one()
561 end
562
563 def fetch_public_activities(opts \\ %{}) do
564 q = fetch_activities_query([Pleroma.Constants.as_public()], opts)
565
566 q
567 |> restrict_unlisted()
568 |> Pagination.fetch_paginated(opts)
569 |> Enum.reverse()
570 end
571
572 @valid_visibilities ~w[direct unlisted public private]
573
574 defp restrict_visibility(query, %{visibility: visibility})
575 when is_list(visibility) do
576 if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
577 query =
578 from(
579 a in query,
580 where:
581 fragment(
582 "activity_visibility(?, ?, ?) = ANY (?)",
583 a.actor,
584 a.recipients,
585 a.data,
586 ^visibility
587 )
588 )
589
590 query
591 else
592 Logger.error("Could not restrict visibility to #{visibility}")
593 end
594 end
595
596 defp restrict_visibility(query, %{visibility: visibility})
597 when visibility in @valid_visibilities do
598 from(
599 a in query,
600 where:
601 fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
602 )
603 end
604
605 defp restrict_visibility(_query, %{visibility: visibility})
606 when visibility not in @valid_visibilities do
607 Logger.error("Could not restrict visibility to #{visibility}")
608 end
609
610 defp restrict_visibility(query, _visibility), do: query
611
612 defp restrict_thread_visibility(query, _, %{skip_thread_containment: true} = _),
613 do: query
614
615 defp restrict_thread_visibility(
616 query,
617 %{"user" => %User{info: %{skip_thread_containment: true}}},
618 _
619 ),
620 do: query
621
622 defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}, _) do
623 from(
624 a in query,
625 where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
626 )
627 end
628
629 defp restrict_thread_visibility(query, _, _), do: query
630
631 def fetch_user_activities(user, reading_user, params \\ %{}) do
632 params =
633 params
634 |> Map.put("type", ["Create", "Announce"])
635 |> Map.put("user", reading_user)
636 |> Map.put("actor_id", user.ap_id)
637 |> Map.put("whole_db", true)
638 |> Map.put("pinned_activity_ids", user.info.pinned_activities)
639
640 recipients =
641 user_activities_recipients(%{
642 "godmode" => params["godmode"],
643 "reading_user" => reading_user
644 })
645
646 fetch_activities(recipients, params)
647 |> Enum.reverse()
648 end
649
650 defp user_activities_recipients(%{"godmode" => true}) do
651 []
652 end
653
654 defp user_activities_recipients(%{"reading_user" => reading_user}) do
655 if reading_user do
656 [Pleroma.Constants.as_public()] ++ [reading_user.ap_id | reading_user.following]
657 else
658 [Pleroma.Constants.as_public()]
659 end
660 end
661
662 defp restrict_since(query, %{"since_id" => ""}), do: query
663
664 defp restrict_since(query, %{"since_id" => since_id}) do
665 from(activity in query, where: activity.id > ^since_id)
666 end
667
668 defp restrict_since(query, _), do: query
669
670 defp restrict_tag_reject(_query, %{"tag_reject" => _tag_reject, "skip_preload" => true}) do
671 raise "Can't use the child object without preloading!"
672 end
673
674 defp restrict_tag_reject(query, %{"tag_reject" => tag_reject})
675 when is_list(tag_reject) and tag_reject != [] do
676 from(
677 [_activity, object] in query,
678 where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
679 )
680 end
681
682 defp restrict_tag_reject(query, _), do: query
683
684 defp restrict_tag_all(_query, %{"tag_all" => _tag_all, "skip_preload" => true}) do
685 raise "Can't use the child object without preloading!"
686 end
687
688 defp restrict_tag_all(query, %{"tag_all" => tag_all})
689 when is_list(tag_all) and tag_all != [] do
690 from(
691 [_activity, object] in query,
692 where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
693 )
694 end
695
696 defp restrict_tag_all(query, _), do: query
697
698 defp restrict_tag(_query, %{"tag" => _tag, "skip_preload" => true}) do
699 raise "Can't use the child object without preloading!"
700 end
701
702 defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do
703 from(
704 [_activity, object] in query,
705 where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag)
706 )
707 end
708
709 defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do
710 from(
711 [_activity, object] in query,
712 where: fragment("(?)->'tag' \\? (?)", object.data, ^tag)
713 )
714 end
715
716 defp restrict_tag(query, _), do: query
717
718 defp restrict_recipients(query, [], _user), do: query
719
720 defp restrict_recipients(query, recipients, nil) do
721 from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
722 end
723
724 defp restrict_recipients(query, recipients, user) do
725 from(
726 activity in query,
727 where: fragment("? && ?", ^recipients, activity.recipients),
728 or_where: activity.actor == ^user.ap_id
729 )
730 end
731
732 defp restrict_local(query, %{"local_only" => true}) do
733 from(activity in query, where: activity.local == true)
734 end
735
736 defp restrict_local(query, _), do: query
737
738 defp restrict_actor(query, %{"actor_id" => actor_id}) do
739 from(activity in query, where: activity.actor == ^actor_id)
740 end
741
742 defp restrict_actor(query, _), do: query
743
744 defp restrict_type(query, %{"type" => type}) when is_binary(type) do
745 from(activity in query, where: fragment("?->>'type' = ?", activity.data, ^type))
746 end
747
748 defp restrict_type(query, %{"type" => type}) do
749 from(activity in query, where: fragment("?->>'type' = ANY(?)", activity.data, ^type))
750 end
751
752 defp restrict_type(query, _), do: query
753
754 defp restrict_state(query, %{"state" => state}) do
755 from(activity in query, where: fragment("?->>'state' = ?", activity.data, ^state))
756 end
757
758 defp restrict_state(query, _), do: query
759
760 defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
761 from(
762 [_activity, object] in query,
763 where: fragment("(?)->'likes' \\? (?)", object.data, ^ap_id)
764 )
765 end
766
767 defp restrict_favorited_by(query, _), do: query
768
769 defp restrict_media(_query, %{"only_media" => _val, "skip_preload" => true}) do
770 raise "Can't use the child object without preloading!"
771 end
772
773 defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
774 from(
775 [_activity, object] in query,
776 where: fragment("not (?)->'attachment' = (?)", object.data, ^[])
777 )
778 end
779
780 defp restrict_media(query, _), do: query
781
782 defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do
783 from(
784 activity in query,
785 where: fragment("?->'object'->>'inReplyTo' is null", activity.data)
786 )
787 end
788
789 defp restrict_replies(query, _), do: query
790
791 defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val == "true" or val == "1" do
792 from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
793 end
794
795 defp restrict_reblogs(query, _), do: query
796
797 defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
798
799 defp restrict_muted(query, %{"muting_user" => %User{info: info}} = opts) do
800 mutes = info.mutes
801
802 query =
803 from([activity] in query,
804 where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
805 where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
806 )
807
808 unless opts["skip_preload"] do
809 from([thread_mute: tm] in query, where: is_nil(tm))
810 else
811 query
812 end
813 end
814
815 defp restrict_muted(query, _), do: query
816
817 defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
818 blocks = info.blocks || []
819 domain_blocks = info.domain_blocks || []
820
821 query =
822 if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query)
823
824 from(
825 [activity, object: o] in query,
826 where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
827 where: fragment("not (? && ?)", activity.recipients, ^blocks),
828 where:
829 fragment(
830 "not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
831 activity.data,
832 activity.data,
833 ^blocks
834 ),
835 where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks),
836 where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks)
837 )
838 end
839
840 defp restrict_blocked(query, _), do: query
841
842 defp restrict_unlisted(query) do
843 from(
844 activity in query,
845 where:
846 fragment(
847 "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
848 activity.data,
849 ^[Pleroma.Constants.as_public()]
850 )
851 )
852 end
853
854 defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids}) do
855 from(activity in query, where: activity.id in ^ids)
856 end
857
858 defp restrict_pinned(query, _), do: query
859
860 defp restrict_muted_reblogs(query, %{"muting_user" => %User{info: info}}) do
861 muted_reblogs = info.muted_reblogs || []
862
863 from(
864 activity in query,
865 where:
866 fragment(
867 "not ( ?->>'type' = 'Announce' and ? = ANY(?))",
868 activity.data,
869 activity.actor,
870 ^muted_reblogs
871 )
872 )
873 end
874
875 defp restrict_muted_reblogs(query, _), do: query
876
877 defp exclude_poll_votes(query, %{"include_poll_votes" => "true"}), do: query
878
879 defp exclude_poll_votes(query, _) do
880 if has_named_binding?(query, :object) do
881 from([activity, object: o] in query,
882 where: fragment("not(?->>'type' = ?)", o.data, "Answer")
883 )
884 else
885 query
886 end
887 end
888
889 defp exclude_id(query, %{"exclude_id" => id}) when is_binary(id) do
890 from(activity in query, where: activity.id != ^id)
891 end
892
893 defp exclude_id(query, _), do: query
894
895 defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
896
897 defp maybe_preload_objects(query, _) do
898 query
899 |> Activity.with_preloaded_object()
900 end
901
902 defp maybe_preload_bookmarks(query, %{"skip_preload" => true}), do: query
903
904 defp maybe_preload_bookmarks(query, opts) do
905 query
906 |> Activity.with_preloaded_bookmark(opts["user"])
907 end
908
909 defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query
910
911 defp maybe_set_thread_muted_field(query, opts) do
912 query
913 |> Activity.with_set_thread_muted_field(opts["muting_user"] || opts["user"])
914 end
915
916 defp maybe_order(query, %{order: :desc}) do
917 query
918 |> order_by(desc: :id)
919 end
920
921 defp maybe_order(query, %{order: :asc}) do
922 query
923 |> order_by(asc: :id)
924 end
925
926 defp maybe_order(query, _), do: query
927
928 def fetch_activities_query(recipients, opts \\ %{}) do
929 config = %{
930 skip_thread_containment: Config.get([:instance, :skip_thread_containment])
931 }
932
933 Activity
934 |> maybe_preload_objects(opts)
935 |> maybe_preload_bookmarks(opts)
936 |> maybe_set_thread_muted_field(opts)
937 |> maybe_order(opts)
938 |> restrict_recipients(recipients, opts["user"])
939 |> restrict_tag(opts)
940 |> restrict_tag_reject(opts)
941 |> restrict_tag_all(opts)
942 |> restrict_since(opts)
943 |> restrict_local(opts)
944 |> restrict_actor(opts)
945 |> restrict_type(opts)
946 |> restrict_state(opts)
947 |> restrict_favorited_by(opts)
948 |> restrict_blocked(opts)
949 |> restrict_muted(opts)
950 |> restrict_media(opts)
951 |> restrict_visibility(opts)
952 |> restrict_thread_visibility(opts, config)
953 |> restrict_replies(opts)
954 |> restrict_reblogs(opts)
955 |> restrict_pinned(opts)
956 |> restrict_muted_reblogs(opts)
957 |> Activity.restrict_deactivated_users()
958 |> exclude_poll_votes(opts)
959 end
960
961 def fetch_activities(recipients, opts \\ %{}) do
962 list_memberships = Pleroma.List.memberships(opts["user"])
963
964 fetch_activities_query(recipients ++ list_memberships, opts)
965 |> Pagination.fetch_paginated(opts)
966 |> Enum.reverse()
967 |> maybe_update_cc(list_memberships, opts["user"])
968 end
969
970 defp maybe_update_cc(activities, list_memberships, %User{ap_id: user_ap_id})
971 when is_list(list_memberships) and length(list_memberships) > 0 do
972 Enum.map(activities, fn
973 %{data: %{"bcc" => bcc}} = activity when is_list(bcc) and length(bcc) > 0 ->
974 if Enum.any?(bcc, &(&1 in list_memberships)) do
975 update_in(activity.data["cc"], &[user_ap_id | &1])
976 else
977 activity
978 end
979
980 activity ->
981 activity
982 end)
983 end
984
985 defp maybe_update_cc(activities, _, _), do: activities
986
987 def fetch_activities_bounded_query(query, recipients, recipients_with_public) do
988 from(activity in query,
989 where:
990 fragment("? && ?", activity.recipients, ^recipients) or
991 (fragment("? && ?", activity.recipients, ^recipients_with_public) and
992 ^Pleroma.Constants.as_public() in activity.recipients)
993 )
994 end
995
996 def fetch_activities_bounded(recipients, recipients_with_public, opts \\ %{}) do
997 fetch_activities_query([], opts)
998 |> fetch_activities_bounded_query(recipients, recipients_with_public)
999 |> Pagination.fetch_paginated(opts)
1000 |> Enum.reverse()
1001 end
1002
1003 def upload(file, opts \\ []) do
1004 with {:ok, data} <- Upload.store(file, opts) do
1005 obj_data =
1006 if opts[:actor] do
1007 Map.put(data, "actor", opts[:actor])
1008 else
1009 data
1010 end
1011
1012 Repo.insert(%Object{data: obj_data})
1013 end
1014 end
1015
1016 defp object_to_user_data(data) do
1017 avatar =
1018 data["icon"]["url"] &&
1019 %{
1020 "type" => "Image",
1021 "url" => [%{"href" => data["icon"]["url"]}]
1022 }
1023
1024 banner =
1025 data["image"]["url"] &&
1026 %{
1027 "type" => "Image",
1028 "url" => [%{"href" => data["image"]["url"]}]
1029 }
1030
1031 fields =
1032 data
1033 |> Map.get("attachment", [])
1034 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1035 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1036
1037 locked = data["manuallyApprovesFollowers"] || false
1038 data = Transmogrifier.maybe_fix_user_object(data)
1039
1040 user_data = %{
1041 ap_id: data["id"],
1042 info: %{
1043 ap_enabled: true,
1044 source_data: data,
1045 banner: banner,
1046 fields: fields,
1047 locked: locked
1048 },
1049 avatar: avatar,
1050 name: data["name"],
1051 follower_address: data["followers"],
1052 following_address: data["following"],
1053 bio: data["summary"]
1054 }
1055
1056 # nickname can be nil because of virtual actors
1057 user_data =
1058 if data["preferredUsername"] do
1059 Map.put(
1060 user_data,
1061 :nickname,
1062 "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
1063 )
1064 else
1065 Map.put(user_data, :nickname, nil)
1066 end
1067
1068 {:ok, user_data}
1069 end
1070
1071 def fetch_follow_information_for_user(user) do
1072 with {:ok, following_data} <-
1073 Fetcher.fetch_and_contain_remote_object_from_id(user.following_address),
1074 following_count when is_integer(following_count) <- following_data["totalItems"],
1075 {:ok, hide_follows} <- collection_private(following_data),
1076 {:ok, followers_data} <-
1077 Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address),
1078 followers_count when is_integer(followers_count) <- followers_data["totalItems"],
1079 {:ok, hide_followers} <- collection_private(followers_data) do
1080 {:ok,
1081 %{
1082 hide_follows: hide_follows,
1083 follower_count: followers_count,
1084 following_count: following_count,
1085 hide_followers: hide_followers
1086 }}
1087 else
1088 {:error, _} = e ->
1089 e
1090
1091 e ->
1092 {:error, e}
1093 end
1094 end
1095
1096 defp maybe_update_follow_information(data) do
1097 with {:enabled, true} <-
1098 {:enabled, Pleroma.Config.get([:instance, :external_user_synchronization])},
1099 {:ok, info} <- fetch_follow_information_for_user(data) do
1100 info = Map.merge(data.info, info)
1101 Map.put(data, :info, info)
1102 else
1103 {:enabled, false} ->
1104 data
1105
1106 e ->
1107 Logger.error(
1108 "Follower/Following counter update for #{data.ap_id} failed.\n" <> inspect(e)
1109 )
1110
1111 data
1112 end
1113 end
1114
1115 defp collection_private(data) do
1116 if is_map(data["first"]) and
1117 data["first"]["type"] in ["CollectionPage", "OrderedCollectionPage"] do
1118 {:ok, false}
1119 else
1120 with {:ok, %{"type" => type}} when type in ["CollectionPage", "OrderedCollectionPage"] <-
1121 Fetcher.fetch_and_contain_remote_object_from_id(data["first"]) do
1122 {:ok, false}
1123 else
1124 {:error, {:ok, %{status: code}}} when code in [401, 403] ->
1125 {:ok, true}
1126
1127 {:error, _} = e ->
1128 e
1129
1130 e ->
1131 {:error, e}
1132 end
1133 end
1134 end
1135
1136 def user_data_from_user_object(data) do
1137 with {:ok, data} <- MRF.filter(data),
1138 {:ok, data} <- object_to_user_data(data) do
1139 {:ok, data}
1140 else
1141 e -> {:error, e}
1142 end
1143 end
1144
1145 def fetch_and_prepare_user_from_ap_id(ap_id) do
1146 with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
1147 {:ok, data} <- user_data_from_user_object(data),
1148 data <- maybe_update_follow_information(data) do
1149 {:ok, data}
1150 else
1151 e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
1152 end
1153 end
1154
1155 def make_user_from_ap_id(ap_id) do
1156 if _user = User.get_cached_by_ap_id(ap_id) do
1157 Transmogrifier.upgrade_user_from_ap_id(ap_id)
1158 else
1159 with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
1160 User.insert_or_update_user(data)
1161 else
1162 e -> {:error, e}
1163 end
1164 end
1165 end
1166
1167 def make_user_from_nickname(nickname) do
1168 with {:ok, %{"ap_id" => ap_id}} when not is_nil(ap_id) <- WebFinger.finger(nickname) do
1169 make_user_from_ap_id(ap_id)
1170 else
1171 _e -> {:error, "No AP id in WebFinger"}
1172 end
1173 end
1174
1175 # filter out broken threads
1176 def contain_broken_threads(%Activity{} = activity, %User{} = user) do
1177 entire_thread_visible_for_user?(activity, user)
1178 end
1179
1180 # do post-processing on a specific activity
1181 def contain_activity(%Activity{} = activity, %User{} = user) do
1182 contain_broken_threads(activity, user)
1183 end
1184
1185 def fetch_direct_messages_query do
1186 Activity
1187 |> restrict_type(%{"type" => "Create"})
1188 |> restrict_visibility(%{visibility: "direct"})
1189 |> order_by([activity], asc: activity.id)
1190 end
1191 end