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