d06bc64eac30d515f965fe87eb9b466e8150a23a
[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.Instances
8 alias Pleroma.Notification
9 alias Pleroma.Object
10 alias Pleroma.Object.Fetcher
11 alias Pleroma.Pagination
12 alias Pleroma.Repo
13 alias Pleroma.Upload
14 alias Pleroma.User
15 alias Pleroma.Web.ActivityPub.MRF
16 alias Pleroma.Web.ActivityPub.Transmogrifier
17 alias Pleroma.Web.Federator
18 alias Pleroma.Web.WebFinger
19
20 import Ecto.Query
21 import Pleroma.Web.ActivityPub.Utils
22 import Pleroma.Web.ActivityPub.Visibility
23
24 require Logger
25
26 @httpoison Application.get_env(:pleroma, :httpoison)
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 = data["to"] || []
32 cc = data["cc"] || []
33 actor = User.get_cached_by_ap_id(data["actor"])
34
35 recipients =
36 (to ++ cc)
37 |> Enum.filter(fn recipient ->
38 case User.get_cached_by_ap_id(recipient) do
39 nil ->
40 true
41
42 user ->
43 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 = data["to"] || []
52 cc = data["cc"] || []
53 actor = data["actor"] || []
54 recipients = (to ++ cc ++ [actor]) |> Enum.uniq()
55 {recipients, to, cc}
56 end
57
58 defp get_recipients(data) do
59 to = data["to"] || []
60 cc = data["cc"] || []
61 recipients = to ++ cc
62 {recipients, to, cc}
63 end
64
65 defp check_actor_is_active(actor) do
66 if not is_nil(actor) do
67 with user <- User.get_cached_by_ap_id(actor),
68 false <- user.info.deactivated do
69 :ok
70 else
71 _e -> :reject
72 end
73 else
74 :ok
75 end
76 end
77
78 defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do
79 limit = Pleroma.Config.get([:instance, :remote_limit])
80 String.length(content) <= limit
81 end
82
83 defp check_remote_limit(_), do: true
84
85 def increase_note_count_if_public(actor, object) do
86 if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
87 end
88
89 def decrease_note_count_if_public(actor, object) do
90 if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
91 end
92
93 def increase_replies_count_if_reply(%{
94 "object" => %{"inReplyTo" => reply_ap_id} = object,
95 "type" => "Create"
96 }) do
97 if is_public?(object) do
98 Object.increase_replies_count(reply_ap_id)
99 end
100 end
101
102 def increase_replies_count_if_reply(_create_data), do: :noop
103
104 def decrease_replies_count_if_reply(%Object{
105 data: %{"inReplyTo" => reply_ap_id} = object
106 }) do
107 if is_public?(object) do
108 Object.decrease_replies_count(reply_ap_id)
109 end
110 end
111
112 def decrease_replies_count_if_reply(_object), do: :noop
113
114 def insert(map, local \\ true, fake \\ false) when is_map(map) do
115 with nil <- Activity.normalize(map),
116 map <- lazy_put_activity_defaults(map, fake),
117 :ok <- check_actor_is_active(map["actor"]),
118 {_, true} <- {:remote_limit_error, check_remote_limit(map)},
119 {:ok, map} <- MRF.filter(map),
120 {recipients, _, _} = get_recipients(map),
121 {:fake, false, map, recipients} <- {:fake, fake, map, recipients},
122 {:ok, map, object} <- insert_full_object(map) do
123 {:ok, activity} =
124 Repo.insert(%Activity{
125 data: map,
126 local: local,
127 actor: map["actor"],
128 recipients: recipients
129 })
130
131 # Splice in the child object if we have one.
132 activity =
133 if !is_nil(object) do
134 Map.put(activity, :object, object)
135 else
136 activity
137 end
138
139 Task.start(fn ->
140 Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
141 end)
142
143 Notification.create_notifications(activity)
144 stream_out(activity)
145 {:ok, activity}
146 else
147 %Activity{} = activity ->
148 {:ok, activity}
149
150 {:fake, true, map, recipients} ->
151 activity = %Activity{
152 data: map,
153 local: local,
154 actor: map["actor"],
155 recipients: recipients,
156 id: "pleroma:fakeid"
157 }
158
159 Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
160 {:ok, activity}
161
162 error ->
163 {:error, error}
164 end
165 end
166
167 def stream_out(activity) do
168 public = "https://www.w3.org/ns/activitystreams#Public"
169
170 if activity.data["type"] in ["Create", "Announce", "Delete"] do
171 Pleroma.Web.Streamer.stream("user", activity)
172 Pleroma.Web.Streamer.stream("list", activity)
173
174 if Enum.member?(activity.data["to"], public) do
175 Pleroma.Web.Streamer.stream("public", activity)
176
177 if activity.local do
178 Pleroma.Web.Streamer.stream("public:local", activity)
179 end
180
181 if activity.data["type"] in ["Create"] do
182 object = Object.normalize(activity)
183
184 object.data
185 |> Map.get("tag", [])
186 |> Enum.filter(fn tag -> is_bitstring(tag) end)
187 |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)
188
189 if object.data["attachment"] != [] do
190 Pleroma.Web.Streamer.stream("public:media", activity)
191
192 if activity.local do
193 Pleroma.Web.Streamer.stream("public:local:media", activity)
194 end
195 end
196 end
197 else
198 if !Enum.member?(activity.data["cc"] || [], public) &&
199 !Enum.member?(
200 activity.data["to"],
201 User.get_cached_by_ap_id(activity.data["actor"]).follower_address
202 ),
203 do: Pleroma.Web.Streamer.stream("direct", activity)
204 end
205 end
206 end
207
208 def create(%{to: to, actor: actor, context: context, object: object} = params, fake \\ false) do
209 additional = params[:additional] || %{}
210 # only accept false as false value
211 local = !(params[:local] == false)
212 published = params[:published]
213
214 with create_data <-
215 make_create_data(
216 %{to: to, actor: actor, published: published, context: context, object: object},
217 additional
218 ),
219 {:ok, activity} <- insert(create_data, local, fake),
220 {:fake, false, activity} <- {:fake, fake, activity},
221 _ <- increase_replies_count_if_reply(create_data),
222 # Changing note count prior to enqueuing federation task in order to avoid
223 # race conditions on updating user.info
224 {:ok, _actor} <- increase_note_count_if_public(actor, activity),
225 :ok <- maybe_federate(activity) do
226 {:ok, activity}
227 else
228 {:fake, true, activity} ->
229 {:ok, activity}
230 end
231 end
232
233 def accept(%{to: to, actor: actor, object: object} = params) do
234 # only accept false as false value
235 local = !(params[:local] == false)
236
237 with data <- %{"to" => to, "type" => "Accept", "actor" => actor.ap_id, "object" => object},
238 {:ok, activity} <- insert(data, local),
239 :ok <- maybe_federate(activity) do
240 {:ok, activity}
241 end
242 end
243
244 def reject(%{to: to, actor: actor, object: object} = params) do
245 # only accept false as false value
246 local = !(params[:local] == false)
247
248 with data <- %{"to" => to, "type" => "Reject", "actor" => actor.ap_id, "object" => object},
249 {:ok, activity} <- insert(data, local),
250 :ok <- maybe_federate(activity) do
251 {:ok, activity}
252 end
253 end
254
255 def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
256 # only accept false as false value
257 local = !(params[:local] == false)
258
259 with data <- %{
260 "to" => to,
261 "cc" => cc,
262 "type" => "Update",
263 "actor" => actor,
264 "object" => object
265 },
266 {:ok, activity} <- insert(data, local),
267 :ok <- maybe_federate(activity) do
268 {:ok, activity}
269 end
270 end
271
272 # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
273 def like(
274 %User{ap_id: ap_id} = user,
275 %Object{data: %{"id" => _}} = object,
276 activity_id \\ nil,
277 local \\ true
278 ) do
279 with nil <- get_existing_like(ap_id, object),
280 like_data <- make_like_data(user, object, activity_id),
281 {:ok, activity} <- insert(like_data, local),
282 {:ok, object} <- add_like_to_object(activity, object),
283 :ok <- maybe_federate(activity) do
284 {:ok, activity, object}
285 else
286 %Activity{} = activity -> {:ok, activity, object}
287 error -> {:error, error}
288 end
289 end
290
291 def unlike(
292 %User{} = actor,
293 %Object{} = object,
294 activity_id \\ nil,
295 local \\ true
296 ) do
297 with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),
298 unlike_data <- make_unlike_data(actor, like_activity, activity_id),
299 {:ok, unlike_activity} <- insert(unlike_data, local),
300 {:ok, _activity} <- Repo.delete(like_activity),
301 {:ok, object} <- remove_like_from_object(like_activity, object),
302 :ok <- maybe_federate(unlike_activity) do
303 {:ok, unlike_activity, like_activity, object}
304 else
305 _e -> {:ok, object}
306 end
307 end
308
309 def announce(
310 %User{ap_id: _} = user,
311 %Object{data: %{"id" => _}} = object,
312 activity_id \\ nil,
313 local \\ true,
314 public \\ true
315 ) do
316 with true <- is_public?(object),
317 announce_data <- make_announce_data(user, object, activity_id, public),
318 {:ok, activity} <- insert(announce_data, local),
319 {:ok, object} <- add_announce_to_object(activity, object),
320 :ok <- maybe_federate(activity) do
321 {:ok, activity, object}
322 else
323 error -> {:error, error}
324 end
325 end
326
327 def unannounce(
328 %User{} = actor,
329 %Object{} = object,
330 activity_id \\ nil,
331 local \\ true
332 ) do
333 with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
334 unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
335 {:ok, unannounce_activity} <- insert(unannounce_data, local),
336 :ok <- maybe_federate(unannounce_activity),
337 {:ok, _activity} <- Repo.delete(announce_activity),
338 {:ok, object} <- remove_announce_from_object(announce_activity, object) do
339 {:ok, unannounce_activity, object}
340 else
341 _e -> {:ok, object}
342 end
343 end
344
345 def follow(follower, followed, activity_id \\ nil, local \\ true) do
346 with data <- make_follow_data(follower, followed, activity_id),
347 {:ok, activity} <- insert(data, local),
348 :ok <- maybe_federate(activity) do
349 {:ok, activity}
350 end
351 end
352
353 def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
354 with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
355 {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
356 unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
357 {:ok, activity} <- insert(unfollow_data, local),
358 :ok <- maybe_federate(activity) do
359 {:ok, activity}
360 end
361 end
362
363 def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
364 user = User.get_cached_by_ap_id(actor)
365 to = (object.data["to"] || []) ++ (object.data["cc"] || [])
366
367 with {:ok, object, activity} <- Object.delete(object),
368 data <- %{
369 "type" => "Delete",
370 "actor" => actor,
371 "object" => id,
372 "to" => to,
373 "deleted_activity_id" => activity && activity.id
374 },
375 {:ok, activity} <- insert(data, local),
376 _ <- decrease_replies_count_if_reply(object),
377 # Changing note count prior to enqueuing federation task in order to avoid
378 # race conditions on updating user.info
379 {:ok, _actor} <- decrease_note_count_if_public(user, object),
380 :ok <- maybe_federate(activity) do
381 {:ok, activity}
382 end
383 end
384
385 def block(blocker, blocked, activity_id \\ nil, local \\ true) do
386 ap_config = Application.get_env(:pleroma, :activitypub)
387 unfollow_blocked = Keyword.get(ap_config, :unfollow_blocked)
388 outgoing_blocks = Keyword.get(ap_config, :outgoing_blocks)
389
390 with true <- unfollow_blocked do
391 follow_activity = fetch_latest_follow(blocker, blocked)
392
393 if follow_activity do
394 unfollow(blocker, blocked, nil, local)
395 end
396 end
397
398 with true <- outgoing_blocks,
399 block_data <- make_block_data(blocker, blocked, activity_id),
400 {:ok, activity} <- insert(block_data, local),
401 :ok <- maybe_federate(activity) do
402 {:ok, activity}
403 else
404 _e -> {:ok, nil}
405 end
406 end
407
408 def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
409 with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
410 unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
411 {:ok, activity} <- insert(unblock_data, local),
412 :ok <- maybe_federate(activity) do
413 {:ok, activity}
414 end
415 end
416
417 def flag(
418 %{
419 actor: actor,
420 context: context,
421 account: account,
422 statuses: statuses,
423 content: content
424 } = params
425 ) do
426 # only accept false as false value
427 local = !(params[:local] == false)
428 forward = !(params[:forward] == false)
429
430 additional = params[:additional] || %{}
431
432 params = %{
433 actor: actor,
434 context: context,
435 account: account,
436 statuses: statuses,
437 content: content
438 }
439
440 additional =
441 if forward do
442 Map.merge(additional, %{"to" => [], "cc" => [account.ap_id]})
443 else
444 Map.merge(additional, %{"to" => [], "cc" => []})
445 end
446
447 with flag_data <- make_flag_data(params, additional),
448 {:ok, activity} <- insert(flag_data, local),
449 :ok <- maybe_federate(activity) do
450 Enum.each(User.all_superusers(), fn superuser ->
451 superuser
452 |> Pleroma.Emails.AdminEmail.report(actor, account, statuses, content)
453 |> Pleroma.Emails.Mailer.deliver_async()
454 end)
455
456 {:ok, activity}
457 end
458 end
459
460 def fetch_activities_for_context(context, opts \\ %{}) do
461 public = ["https://www.w3.org/ns/activitystreams#Public"]
462
463 recipients =
464 if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
465
466 query = from(activity in Activity)
467
468 query =
469 query
470 |> restrict_blocked(opts)
471 |> restrict_recipients(recipients, opts["user"])
472
473 query =
474 from(
475 activity in query,
476 where:
477 fragment(
478 "?->>'type' = ? and ?->>'context' = ?",
479 activity.data,
480 "Create",
481 activity.data,
482 ^context
483 ),
484 order_by: [desc: :id]
485 )
486 |> Activity.with_preloaded_object()
487
488 Repo.all(query)
489 end
490
491 def fetch_public_activities(opts \\ %{}) do
492 q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts)
493
494 q
495 |> restrict_unlisted()
496 |> Pagination.fetch_paginated(opts)
497 |> Enum.reverse()
498 end
499
500 @valid_visibilities ~w[direct unlisted public private]
501
502 defp restrict_visibility(query, %{visibility: visibility})
503 when is_list(visibility) do
504 if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
505 query =
506 from(
507 a in query,
508 where:
509 fragment(
510 "activity_visibility(?, ?, ?) = ANY (?)",
511 a.actor,
512 a.recipients,
513 a.data,
514 ^visibility
515 )
516 )
517
518 Ecto.Adapters.SQL.to_sql(:all, Repo, query)
519
520 query
521 else
522 Logger.error("Could not restrict visibility to #{visibility}")
523 end
524 end
525
526 defp restrict_visibility(query, %{visibility: visibility})
527 when visibility in @valid_visibilities do
528 query =
529 from(
530 a in query,
531 where:
532 fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
533 )
534
535 Ecto.Adapters.SQL.to_sql(:all, Repo, query)
536
537 query
538 end
539
540 defp restrict_visibility(_query, %{visibility: visibility})
541 when visibility not in @valid_visibilities do
542 Logger.error("Could not restrict visibility to #{visibility}")
543 end
544
545 defp restrict_visibility(query, _visibility), do: query
546
547 def fetch_user_activities(user, reading_user, params \\ %{}) do
548 params =
549 params
550 |> Map.put("type", ["Create", "Announce"])
551 |> Map.put("actor_id", user.ap_id)
552 |> Map.put("whole_db", true)
553 |> Map.put("pinned_activity_ids", user.info.pinned_activities)
554
555 recipients =
556 if reading_user do
557 ["https://www.w3.org/ns/activitystreams#Public"] ++
558 [reading_user.ap_id | reading_user.following]
559 else
560 ["https://www.w3.org/ns/activitystreams#Public"]
561 end
562
563 fetch_activities(recipients, params)
564 |> Enum.reverse()
565 end
566
567 defp restrict_since(query, %{"since_id" => ""}), do: query
568
569 defp restrict_since(query, %{"since_id" => since_id}) do
570 from(activity in query, where: activity.id > ^since_id)
571 end
572
573 defp restrict_since(query, _), do: query
574
575 defp restrict_tag_reject(_query, %{"tag_reject" => _tag_reject, "skip_preload" => true}) do
576 raise "Can't use the child object without preloading!"
577 end
578
579 defp restrict_tag_reject(query, %{"tag_reject" => tag_reject})
580 when is_list(tag_reject) and tag_reject != [] do
581 from(
582 [_activity, object] in query,
583 where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
584 )
585 end
586
587 defp restrict_tag_reject(query, _), do: query
588
589 defp restrict_tag_all(_query, %{"tag_all" => _tag_all, "skip_preload" => true}) do
590 raise "Can't use the child object without preloading!"
591 end
592
593 defp restrict_tag_all(query, %{"tag_all" => tag_all})
594 when is_list(tag_all) and tag_all != [] do
595 from(
596 [_activity, object] in query,
597 where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
598 )
599 end
600
601 defp restrict_tag_all(query, _), do: query
602
603 defp restrict_tag(_query, %{"tag" => _tag, "skip_preload" => true}) do
604 raise "Can't use the child object without preloading!"
605 end
606
607 defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do
608 from(
609 [_activity, object] in query,
610 where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag)
611 )
612 end
613
614 defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do
615 from(
616 [_activity, object] in query,
617 where: fragment("(?)->'tag' \\? (?)", object.data, ^tag)
618 )
619 end
620
621 defp restrict_tag(query, _), do: query
622
623 defp restrict_to_cc(query, recipients_to, recipients_cc) do
624 from(
625 activity in query,
626 where:
627 fragment(
628 "(?->'to' \\?| ?) or (?->'cc' \\?| ?)",
629 activity.data,
630 ^recipients_to,
631 activity.data,
632 ^recipients_cc
633 )
634 )
635 end
636
637 defp restrict_recipients(query, [], _user), do: query
638
639 defp restrict_recipients(query, recipients, nil) do
640 from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
641 end
642
643 defp restrict_recipients(query, recipients, user) do
644 from(
645 activity in query,
646 where: fragment("? && ?", ^recipients, activity.recipients),
647 or_where: activity.actor == ^user.ap_id
648 )
649 end
650
651 defp restrict_local(query, %{"local_only" => true}) do
652 from(activity in query, where: activity.local == true)
653 end
654
655 defp restrict_local(query, _), do: query
656
657 defp restrict_actor(query, %{"actor_id" => actor_id}) do
658 from(activity in query, where: activity.actor == ^actor_id)
659 end
660
661 defp restrict_actor(query, _), do: query
662
663 defp restrict_type(query, %{"type" => type}) when is_binary(type) do
664 from(activity in query, where: fragment("?->>'type' = ?", activity.data, ^type))
665 end
666
667 defp restrict_type(query, %{"type" => type}) do
668 from(activity in query, where: fragment("?->>'type' = ANY(?)", activity.data, ^type))
669 end
670
671 defp restrict_type(query, _), do: query
672
673 defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
674 from(
675 activity in query,
676 where: fragment(~s(? <@ (? #> '{"object","likes"}'\)), ^ap_id, activity.data)
677 )
678 end
679
680 defp restrict_favorited_by(query, _), do: query
681
682 defp restrict_media(_query, %{"only_media" => _val, "skip_preload" => true}) do
683 raise "Can't use the child object without preloading!"
684 end
685
686 defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
687 from(
688 [_activity, object] in query,
689 where: fragment("not (?)->'attachment' = (?)", object.data, ^[])
690 )
691 end
692
693 defp restrict_media(query, _), do: query
694
695 defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do
696 from(
697 activity in query,
698 where: fragment("?->'object'->>'inReplyTo' is null", activity.data)
699 )
700 end
701
702 defp restrict_replies(query, _), do: query
703
704 defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val == "true" or val == "1" do
705 from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
706 end
707
708 defp restrict_reblogs(query, _), do: query
709
710 defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
711
712 defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do
713 mutes = info.mutes
714
715 from(
716 activity in query,
717 where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
718 where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
719 )
720 end
721
722 defp restrict_muted(query, _), do: query
723
724 defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
725 blocks = info.blocks || []
726 domain_blocks = info.domain_blocks || []
727
728 from(
729 activity in query,
730 where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
731 where: fragment("not (? && ?)", activity.recipients, ^blocks),
732 where:
733 fragment(
734 "not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
735 activity.data,
736 activity.data,
737 ^blocks
738 ),
739 where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
740 )
741 end
742
743 defp restrict_blocked(query, _), do: query
744
745 defp restrict_unlisted(query) do
746 from(
747 activity in query,
748 where:
749 fragment(
750 "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
751 activity.data,
752 ^["https://www.w3.org/ns/activitystreams#Public"]
753 )
754 )
755 end
756
757 defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids}) do
758 from(activity in query, where: activity.id in ^ids)
759 end
760
761 defp restrict_pinned(query, _), do: query
762
763 defp restrict_muted_reblogs(query, %{"muting_user" => %User{info: info}}) do
764 muted_reblogs = info.muted_reblogs || []
765
766 from(
767 activity in query,
768 where:
769 fragment(
770 "not ( ?->>'type' = 'Announce' and ? = ANY(?))",
771 activity.data,
772 activity.actor,
773 ^muted_reblogs
774 )
775 )
776 end
777
778 defp restrict_muted_reblogs(query, _), do: query
779
780 defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
781
782 defp maybe_preload_objects(query, _) do
783 query
784 |> Activity.with_preloaded_object()
785 end
786
787 def fetch_activities_query(recipients, opts \\ %{}) do
788 base_query = from(activity in Activity)
789
790 base_query
791 |> maybe_preload_objects(opts)
792 |> restrict_recipients(recipients, opts["user"])
793 |> restrict_tag(opts)
794 |> restrict_tag_reject(opts)
795 |> restrict_tag_all(opts)
796 |> restrict_since(opts)
797 |> restrict_local(opts)
798 |> restrict_actor(opts)
799 |> restrict_type(opts)
800 |> restrict_favorited_by(opts)
801 |> restrict_blocked(opts)
802 |> restrict_muted(opts)
803 |> restrict_media(opts)
804 |> restrict_visibility(opts)
805 |> restrict_replies(opts)
806 |> restrict_reblogs(opts)
807 |> restrict_pinned(opts)
808 |> restrict_muted_reblogs(opts)
809 |> Activity.restrict_deactivated_users()
810 end
811
812 def fetch_activities(recipients, opts \\ %{}) do
813 fetch_activities_query(recipients, opts)
814 |> Pagination.fetch_paginated(opts)
815 |> Enum.reverse()
816 end
817
818 def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do
819 fetch_activities_query([], opts)
820 |> restrict_to_cc(recipients_to, recipients_cc)
821 |> Pagination.fetch_paginated(opts)
822 |> Enum.reverse()
823 end
824
825 def upload(file, opts \\ []) do
826 with {:ok, data} <- Upload.store(file, opts) do
827 obj_data =
828 if opts[:actor] do
829 Map.put(data, "actor", opts[:actor])
830 else
831 data
832 end
833
834 Repo.insert(%Object{data: obj_data})
835 end
836 end
837
838 def user_data_from_user_object(data) do
839 avatar =
840 data["icon"]["url"] &&
841 %{
842 "type" => "Image",
843 "url" => [%{"href" => data["icon"]["url"]}]
844 }
845
846 banner =
847 data["image"]["url"] &&
848 %{
849 "type" => "Image",
850 "url" => [%{"href" => data["image"]["url"]}]
851 }
852
853 locked = data["manuallyApprovesFollowers"] || false
854 data = Transmogrifier.maybe_fix_user_object(data)
855
856 user_data = %{
857 ap_id: data["id"],
858 info: %{
859 "ap_enabled" => true,
860 "source_data" => data,
861 "banner" => banner,
862 "locked" => locked
863 },
864 avatar: avatar,
865 name: data["name"],
866 follower_address: data["followers"],
867 bio: data["summary"]
868 }
869
870 # nickname can be nil because of virtual actors
871 user_data =
872 if data["preferredUsername"] do
873 Map.put(
874 user_data,
875 :nickname,
876 "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
877 )
878 else
879 Map.put(user_data, :nickname, nil)
880 end
881
882 {:ok, user_data}
883 end
884
885 def fetch_and_prepare_user_from_ap_id(ap_id) do
886 with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do
887 user_data_from_user_object(data)
888 else
889 e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
890 end
891 end
892
893 def make_user_from_ap_id(ap_id) do
894 if _user = User.get_cached_by_ap_id(ap_id) do
895 Transmogrifier.upgrade_user_from_ap_id(ap_id)
896 else
897 with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
898 User.insert_or_update_user(data)
899 else
900 e -> {:error, e}
901 end
902 end
903 end
904
905 def make_user_from_nickname(nickname) do
906 with {:ok, %{"ap_id" => ap_id}} when not is_nil(ap_id) <- WebFinger.finger(nickname) do
907 make_user_from_ap_id(ap_id)
908 else
909 _e -> {:error, "No AP id in WebFinger"}
910 end
911 end
912
913 def should_federate?(inbox, public) do
914 if public do
915 true
916 else
917 inbox_info = URI.parse(inbox)
918 !Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
919 end
920 end
921
922 def publish(actor, activity) do
923 remote_followers =
924 if actor.follower_address in activity.recipients do
925 {:ok, followers} = User.get_followers(actor)
926 followers |> Enum.filter(&(!&1.local))
927 else
928 []
929 end
930
931 public = is_public?(activity)
932
933 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
934 json = Jason.encode!(data)
935
936 (Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
937 |> Enum.filter(fn user -> User.ap_enabled?(user) end)
938 |> Enum.map(fn %{info: %{source_data: data}} ->
939 (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
940 end)
941 |> Enum.uniq()
942 |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
943 |> Instances.filter_reachable()
944 |> Enum.each(fn {inbox, unreachable_since} ->
945 Federator.publish_single_ap(%{
946 inbox: inbox,
947 json: json,
948 actor: actor,
949 id: activity.data["id"],
950 unreachable_since: unreachable_since
951 })
952 end)
953 end
954
955 def publish_one(%{inbox: inbox, json: json, actor: actor, id: id} = params) do
956 Logger.info("Federating #{id} to #{inbox}")
957 host = URI.parse(inbox).host
958
959 digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
960
961 date =
962 NaiveDateTime.utc_now()
963 |> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
964
965 signature =
966 Pleroma.Web.HTTPSignatures.sign(actor, %{
967 host: host,
968 "content-length": byte_size(json),
969 digest: digest,
970 date: date
971 })
972
973 with {:ok, %{status: code}} when code in 200..299 <-
974 result =
975 @httpoison.post(
976 inbox,
977 json,
978 [
979 {"Content-Type", "application/activity+json"},
980 {"Date", date},
981 {"signature", signature},
982 {"digest", digest}
983 ]
984 ) do
985 if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
986 do: Instances.set_reachable(inbox)
987
988 result
989 else
990 {_post_result, response} ->
991 unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
992 {:error, response}
993 end
994 end
995
996 # filter out broken threads
997 def contain_broken_threads(%Activity{} = activity, %User{} = user) do
998 entire_thread_visible_for_user?(activity, user)
999 end
1000
1001 # do post-processing on a specific activity
1002 def contain_activity(%Activity{} = activity, %User{} = user) do
1003 contain_broken_threads(activity, user)
1004 end
1005
1006 # do post-processing on a timeline
1007 def contain_timeline(timeline, user) do
1008 timeline
1009 |> Enum.filter(fn activity ->
1010 contain_activity(activity, user)
1011 end)
1012 end
1013 end