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