activity_pub.ex 31.3 KB
Newer Older
1
# Pleroma: A lightweight social networking server
kaniini's avatar
kaniini committed
2
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 4
# SPDX-License-Identifier: AGPL-3.0-only

lain's avatar
lain committed
5
defmodule Pleroma.Web.ActivityPub.ActivityPub do
Haelwenn's avatar
Haelwenn committed
6
  alias Pleroma.Activity
Maksim's avatar
Maksim committed
7
  alias Pleroma.Config
8
  alias Pleroma.Conversation
9
  alias Pleroma.Notification
Haelwenn's avatar
Haelwenn committed
10
  alias Pleroma.Object
11
  alias Pleroma.Object.Containment
12
  alias Pleroma.Object.Fetcher
13
  alias Pleroma.Pagination
14
  alias Pleroma.Repo
Haelwenn's avatar
Haelwenn committed
15 16 17
  alias Pleroma.Upload
  alias Pleroma.User
  alias Pleroma.Web.ActivityPub.MRF
18 19
  alias Pleroma.Web.ActivityPub.Transmogrifier
  alias Pleroma.Web.WebFinger
20

lain's avatar
lain committed
21
  import Ecto.Query
lain's avatar
lain committed
22
  import Pleroma.Web.ActivityPub.Utils
lain's avatar
lain committed
23
  import Pleroma.Web.ActivityPub.Visibility
24

lain's avatar
lain committed
25
  require Logger
lain's avatar
lain committed
26

27 28
  # For Announce activities, we filter the recipients based on following status for any actors
  # that match actual users.  See issue #164 for more information about why this is necessary.
29 30 31
  defp get_recipients(%{"type" => "Announce"} = data) do
    to = data["to"] || []
    cc = data["cc"] || []
32 33
    actor = User.get_cached_by_ap_id(data["actor"])

34 35 36 37 38 39 40 41 42 43 44
    recipients =
      (to ++ cc)
      |> Enum.filter(fn recipient ->
        case User.get_cached_by_ap_id(recipient) do
          nil ->
            true

          user ->
            User.following?(user, actor)
        end
      end)
45 46

    {recipients, to, cc}
47 48
  end

Maxim Filippov's avatar
Maxim Filippov committed
49 50 51 52 53 54 55 56
  defp get_recipients(%{"type" => "Create"} = data) do
    to = data["to"] || []
    cc = data["cc"] || []
    actor = data["actor"] || []
    recipients = (to ++ cc ++ [actor]) |> Enum.uniq()
    {recipients, to, cc}
  end

57 58 59 60 61
  defp get_recipients(data) do
    to = data["to"] || []
    cc = data["cc"] || []
    recipients = to ++ cc
    {recipients, to, cc}
lain's avatar
lain committed
62 63
  end

64
  defp check_actor_is_active(actor) do
65 66
    if not is_nil(actor) do
      with user <- User.get_cached_by_ap_id(actor),
lain's avatar
lain committed
67
           false <- user.info.deactivated do
68 69 70 71
        :ok
      else
        _e -> :reject
      end
72 73 74 75 76
    else
      :ok
    end
  end

77
  defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do
Maksim's avatar
Maksim committed
78
    limit = Config.get([:instance, :remote_limit])
79 80 81 82 83
    String.length(content) <= limit
  end

  defp check_remote_limit(_), do: true

84 85 86 87 88 89 90 91
  def increase_note_count_if_public(actor, object) do
    if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
  end

  def decrease_note_count_if_public(actor, object) do
    if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
  end

92
  def increase_replies_count_if_reply(%{
rinpatch's avatar
rinpatch committed
93
        "object" => %{"inReplyTo" => reply_ap_id} = object,
94 95 96 97 98 99 100 101 102 103
        "type" => "Create"
      }) do
    if is_public?(object) do
      Object.increase_replies_count(reply_ap_id)
    end
  end

  def increase_replies_count_if_reply(_create_data), do: :noop

  def decrease_replies_count_if_reply(%Object{
rinpatch's avatar
rinpatch committed
104
        data: %{"inReplyTo" => reply_ap_id} = object
105 106 107 108 109 110 111 112
      }) do
    if is_public?(object) do
      Object.decrease_replies_count(reply_ap_id)
    end
  end

  def decrease_replies_count_if_reply(_object), do: :noop

rinpatch's avatar
rinpatch committed
113 114 115 116 117 118 119 120 121
  def increase_poll_votes_if_vote(%{
        "object" => %{"inReplyTo" => reply_ap_id, "name" => name},
        "type" => "Create"
      }) do
    Object.increase_vote_count(reply_ap_id, name)
  end

  def increase_poll_votes_if_vote(_create_data), do: :noop

rinpatch's avatar
rinpatch committed
122
  def insert(map, local \\ true, fake \\ false) when is_map(map) do
123
    with nil <- Activity.normalize(map),
124
         map <- lazy_put_activity_defaults(map, fake),
125
         :ok <- check_actor_is_active(map["actor"]),
126
         {_, true} <- {:remote_limit_error, check_remote_limit(map)},
href's avatar
href committed
127
         {:ok, map} <- MRF.filter(map),
rinpatch's avatar
rinpatch committed
128 129
         {recipients, _, _} = get_recipients(map),
         {:fake, false, map, recipients} <- {:fake, fake, map, recipients},
130
         :ok <- Containment.contain_child(map),
131
         {:ok, map, object} <- insert_full_object(map) do
lain's avatar
lain committed
132 133 134 135 136
      {:ok, activity} =
        Repo.insert(%Activity{
          data: map,
          local: local,
          actor: map["actor"],
137
          recipients: recipients
lain's avatar
lain committed
138 139
        })

140 141 142 143 144 145 146 147
      # Splice in the child object if we have one.
      activity =
        if !is_nil(object) do
          Map.put(activity, :object, object)
        else
          activity
        end

148
      PleromaJobQueue.enqueue(:background, Pleroma.Web.RichMedia.Helpers, [:fetch, activity])
149

150
      Notification.create_notifications(activity)
151 152 153 154 155 156

      participations =
        activity
        |> Conversation.create_or_bump_for()
        |> get_participations()

lain's avatar
lain committed
157
      stream_out(activity)
158
      stream_out_participations(participations)
159
      {:ok, activity}
lain's avatar
lain committed
160
    else
rinpatch's avatar
rinpatch committed
161 162 163 164
      %Activity{} = activity ->
        {:ok, activity}

      {:fake, true, map, recipients} ->
rinpatch's avatar
rinpatch committed
165 166 167 168 169 170 171 172
        activity = %Activity{
          data: map,
          local: local,
          actor: map["actor"],
          recipients: recipients,
          id: "pleroma:fakeid"
        }

rinpatch's avatar
oof  
rinpatch committed
173
        Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
rinpatch's avatar
rinpatch committed
174
        {:ok, activity}
rinpatch's avatar
rinpatch committed
175 176 177

      error ->
        {:error, error}
lain's avatar
lain committed
178
    end
lain's avatar
lain committed
179
  end
lain's avatar
lain committed
180

181 182 183 184 185 186 187 188 189 190 191 192 193
  defp get_participations({:ok, %{participations: participations}}), do: participations
  defp get_participations(_), do: []

  def stream_out_participations(participations) do
    participations =
      participations
      |> Repo.preload(:user)

    Enum.each(participations, fn participation ->
      Pleroma.Web.Streamer.stream("participation", participation)
    end)
  end

194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
  def stream_out_participations(%Object{data: %{"context" => context}}, user) do
    with %Conversation{} = conversation <- Conversation.get_for_ap_id(context),
         conversation = Repo.preload(conversation, :participations),
         last_activity_id =
           fetch_latest_activity_id_for_context(conversation.ap_id, %{
             "user" => user,
             "blocking_user" => user
           }) do
      if last_activity_id do
        stream_out_participations(conversation.participations)
      end
    end
  end

  def stream_out_participations(_, _), do: :noop

lain's avatar
lain committed
210
  def stream_out(activity) do
csaurus's avatar
csaurus committed
211 212
    public = "https://www.w3.org/ns/activitystreams#Public"

213
    if activity.data["type"] in ["Create", "Announce", "Delete"] do
rinpatch's avatar
rinpatch committed
214 215
      object = Object.normalize(activity)
      # Do not stream out poll replies
216
      unless object.data["type"] == "Answer" do
rinpatch's avatar
rinpatch committed
217 218
        Pleroma.Web.Streamer.stream("user", activity)
        Pleroma.Web.Streamer.stream("list", activity)
lain's avatar
lain committed
219

rinpatch's avatar
rinpatch committed
220 221
        if Enum.member?(activity.data["to"], public) do
          Pleroma.Web.Streamer.stream("public", activity)
222

rinpatch's avatar
rinpatch committed
223 224 225
          if activity.local do
            Pleroma.Web.Streamer.stream("public:local", activity)
          end
226

rinpatch's avatar
rinpatch committed
227 228 229 230 231
          if activity.data["type"] in ["Create"] do
            object.data
            |> Map.get("tag", [])
            |> Enum.filter(fn tag -> is_bitstring(tag) end)
            |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)
232

rinpatch's avatar
rinpatch committed
233 234
            if object.data["attachment"] != [] do
              Pleroma.Web.Streamer.stream("public:media", activity)
235

rinpatch's avatar
rinpatch committed
236 237 238
              if activity.local do
                Pleroma.Web.Streamer.stream("public:local:media", activity)
              end
239
            end
240
          end
rinpatch's avatar
rinpatch committed
241 242 243 244 245 246 247 248
        else
          # TODO: Write test, replace with visibility test
          if !Enum.member?(activity.data["cc"] || [], public) &&
               !Enum.member?(
                 activity.data["to"],
                 User.get_cached_by_ap_id(activity.data["actor"]).follower_address
               ),
             do: Pleroma.Web.Streamer.stream("direct", activity)
249
        end
lain's avatar
lain committed
250
      end
lain's avatar
lain committed
251 252 253
    end
  end

rinpatch's avatar
rinpatch committed
254
  def create(%{to: to, actor: actor, context: context, object: object} = params, fake \\ false) do
lain's avatar
lain committed
255
    additional = params[:additional] || %{}
lain's avatar
lain committed
256 257
    # only accept false as false value
    local = !(params[:local] == false)
lain's avatar
lain committed
258 259
    published = params[:published]

lain's avatar
lain committed
260 261 262 263 264
    with create_data <-
           make_create_data(
             %{to: to, actor: actor, published: published, context: context, object: object},
             additional
           ),
rinpatch's avatar
rinpatch committed
265 266
         {:ok, activity} <- insert(create_data, local, fake),
         {:fake, false, activity} <- {:fake, fake, activity},
267
         _ <- increase_replies_count_if_reply(create_data),
rinpatch's avatar
rinpatch committed
268
         _ <- increase_poll_votes_if_vote(create_data),
269 270
         # Changing note count prior to enqueuing federation task in order to avoid
         # race conditions on updating user.info
271
         {:ok, _actor} <- increase_note_count_if_public(actor, activity),
272
         :ok <- maybe_federate(activity) do
273
      {:ok, activity}
rinpatch's avatar
rinpatch committed
274 275 276
    else
      {:fake, true, activity} ->
        {:ok, activity}
277 278 279

      {:error, message} ->
        {:error, message}
280
    end
lain's avatar
lain committed
281
  end
lain's avatar
lain committed
282

283
  def accept(%{to: to, actor: actor, object: object} = params) do
lain's avatar
lain committed
284 285
    # only accept false as false value
    local = !(params[:local] == false)
286

287
    with data <- %{"to" => to, "type" => "Accept", "actor" => actor.ap_id, "object" => object},
288
         {:ok, activity} <- insert(data, local),
289
         :ok <- maybe_federate(activity) do
290 291 292 293
      {:ok, activity}
    end
  end

294 295 296 297
  def reject(%{to: to, actor: actor, object: object} = params) do
    # only accept false as false value
    local = !(params[:local] == false)

298
    with data <- %{"to" => to, "type" => "Reject", "actor" => actor.ap_id, "object" => object},
299
         {:ok, activity} <- insert(data, local),
300
         :ok <- maybe_federate(activity) do
301 302 303 304
      {:ok, activity}
    end
  end

lain's avatar
lain committed
305
  def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
lain's avatar
lain committed
306 307 308 309 310 311 312 313 314 315
    # only accept false as false value
    local = !(params[:local] == false)

    with data <- %{
           "to" => to,
           "cc" => cc,
           "type" => "Update",
           "actor" => actor,
           "object" => object
         },
lain's avatar
lain committed
316 317 318 319 320 321
         {:ok, activity} <- insert(data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

lain's avatar
lain committed
322
  # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
lain's avatar
lain committed
323 324 325 326 327 328
  def like(
        %User{ap_id: ap_id} = user,
        %Object{data: %{"id" => _}} = object,
        activity_id \\ nil,
        local \\ true
      ) do
lain's avatar
lain committed
329 330 331 332 333 334 335 336 337
    with nil <- get_existing_like(ap_id, object),
         like_data <- make_like_data(user, object, activity_id),
         {:ok, activity} <- insert(like_data, local),
         {:ok, object} <- add_like_to_object(activity, object),
         :ok <- maybe_federate(activity) do
      {:ok, activity, object}
    else
      %Activity{} = activity -> {:ok, activity, object}
      error -> {:error, error}
lain's avatar
lain committed
338
    end
lain's avatar
lain committed
339 340
  end

Thog's avatar
Thog committed
341 342 343 344 345 346 347 348 349 350 351 352 353
  def unlike(
        %User{} = actor,
        %Object{} = object,
        activity_id \\ nil,
        local \\ true
      ) do
    with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),
         unlike_data <- make_unlike_data(actor, like_activity, activity_id),
         {:ok, unlike_activity} <- insert(unlike_data, local),
         {:ok, _activity} <- Repo.delete(like_activity),
         {:ok, object} <- remove_like_from_object(like_activity, object),
         :ok <- maybe_federate(unlike_activity) do
      {:ok, unlike_activity, like_activity, object}
lain's avatar
lain committed
354 355
    else
      _e -> {:ok, object}
lain's avatar
lain committed
356 357 358
    end
  end

lain's avatar
lain committed
359 360 361 362
  def announce(
        %User{ap_id: _} = user,
        %Object{data: %{"id" => _}} = object,
        activity_id \\ nil,
363 364
        local \\ true,
        public \\ true
lain's avatar
lain committed
365
      ) do
lain's avatar
lain committed
366
    with true <- is_public?(object),
367
         announce_data <- make_announce_data(user, object, activity_id, public),
lain's avatar
lain committed
368 369 370 371 372 373 374
         {:ok, activity} <- insert(announce_data, local),
         {:ok, object} <- add_announce_to_object(activity, object),
         :ok <- maybe_federate(activity) do
      {:ok, activity, object}
    else
      error -> {:error, error}
    end
lain's avatar
lain committed
375 376
  end

377 378 379
  def unannounce(
        %User{} = actor,
        %Object{} = object,
380 381
        activity_id \\ nil,
        local \\ true
382
      ) do
normandy's avatar
normandy committed
383 384
    with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
         unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
385
         {:ok, unannounce_activity} <- insert(unannounce_data, local),
normandy's avatar
normandy committed
386 387 388
         :ok <- maybe_federate(unannounce_activity),
         {:ok, _activity} <- Repo.delete(announce_activity),
         {:ok, object} <- remove_announce_from_object(announce_activity, object) do
389
      {:ok, unannounce_activity, object}
normandy's avatar
normandy committed
390 391 392 393 394
    else
      _e -> {:ok, object}
    end
  end

lain's avatar
lain committed
395 396 397
  def follow(follower, followed, activity_id \\ nil, local \\ true) do
    with data <- make_follow_data(follower, followed, activity_id),
         {:ok, activity} <- insert(data, local),
398
         :ok <- maybe_federate(activity) do
lain's avatar
lain committed
399 400
      {:ok, activity}
    end
lain's avatar
lain committed
401 402
  end

normandy's avatar
normandy committed
403
  def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
lain's avatar
lain committed
404
    with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
405
         {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
normandy's avatar
normandy committed
406
         unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
lain's avatar
lain committed
407
         {:ok, activity} <- insert(unfollow_data, local),
408
         :ok <- maybe_federate(activity) do
lain's avatar
lain committed
409 410
      {:ok, activity}
    end
lain's avatar
lain committed
411 412
  end

lain's avatar
lain committed
413 414
  def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
    user = User.get_cached_by_ap_id(actor)
415
    to = (object.data["to"] || []) ++ (object.data["cc"] || [])
Karen Konou's avatar
Karen Konou committed
416

417 418 419 420 421 422 423 424
    with {:ok, object, activity} <- Object.delete(object),
         data <- %{
           "type" => "Delete",
           "actor" => actor,
           "object" => id,
           "to" => to,
           "deleted_activity_id" => activity && activity.id
         },
425 426
         {:ok, activity} <- insert(data, local, false),
         stream_out_participations(object, user),
427
         _ <- decrease_replies_count_if_reply(object),
428 429
         # Changing note count prior to enqueuing federation task in order to avoid
         # race conditions on updating user.info
430
         {:ok, _actor} <- decrease_note_count_if_public(user, object),
431
         :ok <- maybe_federate(activity) do
lain's avatar
lain committed
432 433 434 435
      {:ok, activity}
    end
  end

normandy's avatar
normandy committed
436
  def block(blocker, blocked, activity_id \\ nil, local \\ true) do
Maksim's avatar
Maksim committed
437 438
    outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
    unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
normandy's avatar
normandy committed
439

minibikini's avatar
minibikini committed
440
    if unfollow_blocked do
441
      follow_activity = fetch_latest_follow(blocker, blocked)
minibikini's avatar
minibikini committed
442
      if follow_activity, do: unfollow(blocker, blocked, nil, local)
normandy's avatar
normandy committed
443 444
    end

445 446
    with true <- outgoing_blocks,
         block_data <- make_block_data(blocker, blocked, activity_id),
normandy's avatar
normandy committed
447 448 449
         {:ok, activity} <- insert(block_data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
450
    else
squidboi's avatar
squidboi committed
451
      _e -> {:ok, nil}
normandy's avatar
normandy committed
452 453 454
    end
  end

normandy's avatar
normandy committed
455
  def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
normandy's avatar
normandy committed
456
    with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
normandy's avatar
normandy committed
457
         unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
normandy's avatar
normandy committed
458 459 460 461 462 463
         {:ok, activity} <- insert(unblock_data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

minibikini's avatar
Reports  
minibikini committed
464 465 466 467 468 469 470 471 472 473 474
  def flag(
        %{
          actor: actor,
          context: context,
          account: account,
          statuses: statuses,
          content: content
        } = params
      ) do
    # only accept false as false value
    local = !(params[:local] == false)
475 476 477
    forward = !(params[:forward] == false)

    additional = params[:additional] || %{}
minibikini's avatar
Reports  
minibikini committed
478

479
    params = %{
minibikini's avatar
Reports  
minibikini committed
480 481 482 483 484 485
      actor: actor,
      context: context,
      account: account,
      statuses: statuses,
      content: content
    }
486 487 488 489 490

    additional =
      if forward do
        Map.merge(additional, %{"to" => [], "cc" => [account.ap_id]})
      else
491
        Map.merge(additional, %{"to" => [], "cc" => []})
492 493 494 495 496
      end

    with flag_data <- make_flag_data(params, additional),
         {:ok, activity} <- insert(flag_data, local),
         :ok <- maybe_federate(activity) do
497 498
      Enum.each(User.all_superusers(), fn superuser ->
        superuser
499
        |> Pleroma.Emails.AdminEmail.report(actor, account, statuses, content)
500
        |> Pleroma.Emails.Mailer.deliver_async()
501 502
      end)

503 504
      {:ok, activity}
    end
minibikini's avatar
Reports  
minibikini committed
505 506
  end

507
  defp fetch_activities_for_context_query(context, opts) do
lain's avatar
lain committed
508 509
    public = ["https://www.w3.org/ns/activitystreams#Public"]

lain's avatar
lain committed
510 511 512
    recipients =
      if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public

513
    from(activity in Activity)
514
    |> maybe_preload_objects(opts)
515 516 517 518 519 520 521 522 523 524
    |> restrict_blocked(opts)
    |> restrict_recipients(recipients, opts["user"])
    |> where(
      [activity],
      fragment(
        "?->>'type' = ? and ?->>'context' = ?",
        activity.data,
        "Create",
        activity.data,
        ^context
lain's avatar
lain committed
525
      )
526
    )
527
    |> exclude_poll_votes(opts)
528 529 530 531 532 533 534 535 536
    |> order_by([activity], desc: activity.id)
  end

  @spec fetch_activities_for_context(String.t(), keyword() | map()) :: [Activity.t()]
  def fetch_activities_for_context(context, opts \\ %{}) do
    context
    |> fetch_activities_for_context_query(opts)
    |> Repo.all()
  end
lain's avatar
lain committed
537

538 539 540 541
  @spec fetch_latest_activity_id_for_context(String.t(), keyword() | map()) ::
          Pleroma.FlakeId.t() | nil
  def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
    context
542
    |> fetch_activities_for_context_query(Map.merge(%{"skip_preload" => true}, opts))
543 544 545
    |> limit(1)
    |> select([a], a.id)
    |> Repo.one()
lain's avatar
lain committed
546 547
  end

lain's avatar
lain committed
548
  def fetch_public_activities(opts \\ %{}) do
lain's avatar
lain committed
549
    q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts)
lain's avatar
lain committed
550

lain's avatar
lain committed
551
    q
552
    |> restrict_unlisted()
553
    |> Pagination.fetch_paginated(opts)
lain's avatar
lain committed
554
    |> Enum.reverse()
lain's avatar
lain committed
555 556
  end

557 558
  @valid_visibilities ~w[direct unlisted public private]

559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580
  defp restrict_visibility(query, %{visibility: visibility})
       when is_list(visibility) do
    if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
      query =
        from(
          a in query,
          where:
            fragment(
              "activity_visibility(?, ?, ?) = ANY (?)",
              a.actor,
              a.recipients,
              a.data,
              ^visibility
            )
        )

      query
    else
      Logger.error("Could not restrict visibility to #{visibility}")
    end
  end

lain's avatar
lain committed
581 582
  defp restrict_visibility(query, %{visibility: visibility})
       when visibility in @valid_visibilities do
583 584 585 586 587
    from(
      a in query,
      where:
        fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
    )
588 589 590 591 592 593 594 595 596
  end

  defp restrict_visibility(_query, %{visibility: visibility})
       when visibility not in @valid_visibilities do
    Logger.error("Could not restrict visibility to #{visibility}")
  end

  defp restrict_visibility(query, _visibility), do: query

597 598
  defp restrict_thread_visibility(query, _, %{skip_thread_containment: true} = _),
    do: query
599

600 601 602 603 604 605 606
  defp restrict_thread_visibility(
         query,
         %{"user" => %User{info: %{skip_thread_containment: true}}},
         _
       ),
       do: query

607 608 609 610 611
  defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}, _) do
    from(
      a in query,
      where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
    )
612 613
  end

614
  defp restrict_thread_visibility(query, _, _), do: query
615

616 617 618 619 620 621
  def fetch_user_activities(user, reading_user, params \\ %{}) do
    params =
      params
      |> Map.put("type", ["Create", "Announce"])
      |> Map.put("actor_id", user.ap_id)
      |> Map.put("whole_db", true)
minibikini's avatar
minibikini committed
622
      |> Map.put("pinned_activity_ids", user.info.pinned_activities)
623 624 625 626 627 628 629 630 631 632 633 634 635

    recipients =
      if reading_user do
        ["https://www.w3.org/ns/activitystreams#Public"] ++
          [reading_user.ap_id | reading_user.following]
      else
        ["https://www.w3.org/ns/activitystreams#Public"]
      end

    fetch_activities(recipients, params)
    |> Enum.reverse()
  end

636 637
  defp restrict_since(query, %{"since_id" => ""}), do: query

lain's avatar
lain committed
638
  defp restrict_since(query, %{"since_id" => since_id}) do
lain's avatar
lain committed
639
    from(activity in query, where: activity.id > ^since_id)
lain's avatar
lain committed
640
  end
lain's avatar
lain committed
641

lain's avatar
lain committed
642
  defp restrict_since(query, _), do: query
lain's avatar
lain committed
643

644 645 646 647
  defp restrict_tag_reject(_query, %{"tag_reject" => _tag_reject, "skip_preload" => true}) do
    raise "Can't use the child object without preloading!"
  end

Haelwenn's avatar
Haelwenn committed
648 649
  defp restrict_tag_reject(query, %{"tag_reject" => tag_reject})
       when is_list(tag_reject) and tag_reject != [] do
Haelwenn's avatar
Haelwenn committed
650
    from(
651 652
      [_activity, object] in query,
      where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
653 654 655
    )
  end

Haelwenn's avatar
Haelwenn committed
656 657
  defp restrict_tag_reject(query, _), do: query

658 659 660 661
  defp restrict_tag_all(_query, %{"tag_all" => _tag_all, "skip_preload" => true}) do
    raise "Can't use the child object without preloading!"
  end

Haelwenn's avatar
Haelwenn committed
662 663 664
  defp restrict_tag_all(query, %{"tag_all" => tag_all})
       when is_list(tag_all) and tag_all != [] do
    from(
665 666
      [_activity, object] in query,
      where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
Haelwenn's avatar
Haelwenn committed
667 668 669 670 671
    )
  end

  defp restrict_tag_all(query, _), do: query

672 673 674 675
  defp restrict_tag(_query, %{"tag" => _tag, "skip_preload" => true}) do
    raise "Can't use the child object without preloading!"
  end

676 677
  defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do
    from(
678 679
      [_activity, object] in query,
      where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag)
Haelwenn's avatar
Haelwenn committed
680 681 682
    )
  end

683
  defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do
lain's avatar
lain committed
684
    from(
685 686
      [_activity, object] in query,
      where: fragment("(?)->'tag' \\? (?)", object.data, ^tag)
lain's avatar
lain committed
687
    )
Roger Braun's avatar
Roger Braun committed
688
  end
lain's avatar
lain committed
689

Roger Braun's avatar
Roger Braun committed
690 691
  defp restrict_tag(query, _), do: query

feld's avatar
feld committed
692
  defp restrict_recipients(query, [], _user), do: query
lain's avatar
lain committed
693

lain's avatar
lain committed
694
  defp restrict_recipients(query, recipients, nil) do
lain's avatar
lain committed
695
    from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
lain's avatar
lain committed
696
  end
lain's avatar
lain committed
697

lain's avatar
lain committed
698
  defp restrict_recipients(query, recipients, user) do
lain's avatar
lain committed
699 700
    from(
      activity in query,
lain's avatar
lain committed
701 702
      where: fragment("? && ?", ^recipients, activity.recipients),
      or_where: activity.actor == ^user.ap_id
lain's avatar
lain committed
703
    )
lain's avatar
lain committed
704
  end
lain's avatar
lain committed
705

lain's avatar
lain committed
706
  defp restrict_local(query, %{"local_only" => true}) do
lain's avatar
lain committed
707
    from(activity in query, where: activity.local == true)
lain's avatar
lain committed
708
  end
lain's avatar
lain committed
709

lain's avatar
lain committed
710
  defp restrict_local(query, _), do: query
lain's avatar
lain committed
711

lain's avatar
lain committed
712
  defp restrict_actor(query, %{"actor_id" => actor_id}) do
lain's avatar
lain committed
713
    from(activity in query, where: activity.actor == ^actor_id)
714
  end
lain's avatar
lain committed
715

lain's avatar
lain committed
716
  defp restrict_actor(query, _), do: query
717

lain's avatar
lain committed
718
  defp restrict_type(query, %{"type" => type}) when is_binary(type) do
719
    from(activity in query, where: fragment("?->>'type' = ?", activity.data, ^type))
lain's avatar
lain committed
720
  end
lain's avatar
lain committed
721

722
  defp restrict_type(query, %{"type" => type}) do
lain's avatar
lain committed
723
    from(activity in query, where: fragment("?->>'type' = ANY(?)", activity.data, ^type))
724
  end
lain's avatar
lain committed
725

726 727
  defp restrict_type(query, _), do: query

Sergey Suprunenko's avatar
Sergey Suprunenko committed
728 729 730 731 732 733
  defp restrict_state(query, %{"state" => state}) do
    from(activity in query, where: fragment("?->>'state' = ?", activity.data, ^state))
  end

  defp restrict_state(query, _), do: query

734
  defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
lain's avatar
lain committed
735
    from(
736 737
      [_activity, object] in query,
      where: fragment("(?)->'likes' \\? (?)", object.data, ^ap_id)
lain's avatar
lain committed
738
    )
739
  end
lain's avatar
lain committed
740

741 742
  defp restrict_favorited_by(query, _), do: query

743 744 745 746
  defp restrict_media(_query, %{"only_media" => _val, "skip_preload" => true}) do
    raise "Can't use the child object without preloading!"
  end

eal's avatar
eal committed
747
  defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
lain's avatar
lain committed
748
    from(
749 750
      [_activity, object] in query,
      where: fragment("not (?)->'attachment' = (?)", object.data, ^[])
lain's avatar
lain committed
751
    )
eal's avatar
eal committed
752
  end
lain's avatar
lain committed
753

eal's avatar
eal committed
754 755
  defp restrict_media(query, _), do: query

756 757 758 759 760 761 762 763 764
  defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do
    from(
      activity in query,
      where: fragment("?->'object'->>'inReplyTo' is null", activity.data)
    )
  end

  defp restrict_replies(query, _), do: query

765 766 767 768 769 770
  defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val == "true" or val == "1" do
    from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
  end

  defp restrict_reblogs(query, _), do: query

lain's avatar
lain committed
771 772
  defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query

773
  defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do
774
    mutes = info.mutes
775 776 777 778 779 780 781 782 783 784

    from(
      activity in query,
      where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
      where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
    )
  end

  defp restrict_muted(query, _), do: query

lain's avatar
lain committed
785
  defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
lain's avatar
lain committed
786 787
    blocks = info.blocks || []
    domain_blocks = info.domain_blocks || []
lain's avatar
lain committed
788

789 790 791
    query =
      if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query)

lain's avatar
lain committed
792
    from(
793
      [activity, object: o] in query,
794
      where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
795
      where: fragment("not (? && ?)", activity.recipients, ^blocks),
796 797 798 799 800 801 802
      where:
        fragment(
          "not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
          activity.data,
          activity.data,
          ^blocks
        ),
803 804
      where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks),
      where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks)
805
    )
806
  end
lain's avatar
lain committed
807

808 809
  defp restrict_blocked(query, _), do: query

810 811 812
  defp restrict_unlisted(query) do
    from(
      activity in query,
lain's avatar
Format.  
lain committed
813 814
      where:
        fragment(
lain's avatar
lain committed
815
          "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
lain's avatar
Format.  
lain committed
816 817 818
          activity.data,
          ^["https://www.w3.org/ns/activitystreams#Public"]
        )
819 820 821
    )
  end

minibikini's avatar
minibikini committed
822 823 824 825 826 827
  defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids}) do
    from(activity in query, where: activity.id in ^ids)
  end

  defp restrict_pinned(query, _), do: query

Karen Konou's avatar
Karen Konou committed
828 829 830 831 832
  defp restrict_muted_reblogs(query, %{"muting_user" => %User{info: info}}) do
    muted_reblogs = info.muted_reblogs || []

    from(
      activity in query,
833 834 835 836 837 838 839
      where:
        fragment(
          "not ( ?->>'type' = 'Announce' and ? = ANY(?))",
          activity.data,
          activity.actor,
          ^muted_reblogs
        )
Karen Konou's avatar
Karen Konou committed
840 841 842 843 844
    )
  end

  defp restrict_muted_reblogs(query, _), do: query

845
  defp exclude_poll_votes(query, %{"include_poll_votes" => true}), do: query
846 847 848 849 850 851 852 853 854 855 856

  defp exclude_poll_votes(query, _) do
    if has_named_binding?(query, :object) do
      from([activity, object: o] in query,
        where: fragment("not(?->>'type' = ?)", o.data, "Answer")
      )
    else
      query
    end
  end

857 858 859 860 861 862 863
  defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query

  defp maybe_preload_objects(query, _) do
    query
    |> Activity.with_preloaded_object()
  end

864 865 866 867 868 869 870
  defp maybe_preload_bookmarks(query, %{"skip_preload" => true}), do: query

  defp maybe_preload_bookmarks(query, opts) do
    query
    |> Activity.with_preloaded_bookmark(opts["user"])
  end

Aaron Tinio's avatar
Aaron Tinio committed
871 872 873 874 875 876 877
  defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query

  defp maybe_set_thread_muted_field(query, opts) do
    query
    |> Activity.with_set_thread_muted_field(opts["user"])
  end

878 879 880 881 882 883 884 885 886 887 888 889
  defp maybe_order(query, %{order: :desc}) do
    query
    |> order_by(desc: :id)
  end

  defp maybe_order(query, %{order: :asc}) do
    query
    |> order_by(asc: :id)
  end

  defp maybe_order(query, _), do: query

lain's avatar
lain committed
890
  def fetch_activities_query(recipients, opts \\ %{}) do
891
    base_query = from(activity in Activity)
892 893 894 895

    config = %{
      skip_thread_containment: Config.get([:instance, :skip_thread_containment])
    }
lain's avatar
lain committed
896

lain's avatar
lain committed
897
    base_query
898
    |> maybe_preload_objects(opts)
899
    |> maybe_preload_bookmarks(opts)
Aaron Tinio's avatar
Aaron Tinio committed
900
    |> maybe_set_thread_muted_field(opts)
901
    |> maybe_order(opts)
lain's avatar
lain committed
902
    |> restrict_recipients(recipients, opts["user"])
Roger Braun's avatar
Roger Braun committed
903
    |> restrict_tag(opts)
Haelwenn's avatar
Haelwenn committed
904 905
    |> restrict_tag_reject(opts)
    |> restrict_tag_all(opts)
lain's avatar
lain committed
906 907 908
    |> restrict_since(opts)
    |> restrict_local(opts)
    |> restrict_actor(opts)
909
    |> restrict_type(opts)
Sergey Suprunenko's avatar
Sergey Suprunenko committed
910
    |> restrict_state(opts)
911
    |> restrict_favorited_by(opts)
912
    |> restrict_blocked(opts)
913
    |> restrict_muted(opts)
eal's avatar
eal committed
914
    |> restrict_media(opts)
915
    |> restrict_visibility(opts)
916
    |> restrict_thread_visibility(opts, config)
917
    |> restrict_replies(opts)
918
    |> restrict_reblogs(opts)
minibikini's avatar
minibikini committed
919
    |> restrict_pinned(opts)
Karen Konou's avatar
Karen Konou committed
920
    |> restrict_muted_reblogs(opts)
921
    |> Activity.restrict_deactivated_users()
rinpatch's avatar
rinpatch committed
922
    |> exclude_poll_votes(opts)
lain's avatar
lain committed
923 924 925 926
  end

  def fetch_activities(recipients, opts \\ %{}) do
    fetch_activities_query(recipients, opts)
927
    |> Pagination.fetch_paginated(opts)
lain's avatar
lain committed
928
    |> Enum.reverse()
dtluna's avatar
dtluna committed
929 930
  end

931 932 933 934