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