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