aboutsummaryrefslogtreecommitdiff
path: root/lib/pleroma/web/activity_pub/activity_pub.ex
blob: 79c1d5b6951aedb79b53d6fa8448f20fd367e147 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
defmodule Pleroma.Web.ActivityPub.ActivityPub do
  alias Pleroma.{Activity, Repo, Object, Upload, User, Web}
  alias Ecto.{Changeset, UUID}
  import Ecto.Query
  import Pleroma.Web.ActivityPub.Utils
  require Logger

  def insert(map, local \\ true) when is_map(map) do
    with nil <- Activity.get_by_ap_id(map["id"]),
         map <- lazy_put_activity_defaults(map),
         :ok <- insert_full_object(map) do
      Repo.insert(%Activity{data: map, local: local})
    else
      %Activity{} = activity -> {:ok, activity}
      error -> {:error, error}
    end
  end

  def create(to, actor, context, object, additional \\ %{}, published \\ nil, local \\ true) do
    with create_data <- make_create_data(%{to: to, actor: actor, published: published, context: context, object: object}, additional),
         {:ok, activity} <- insert(create_data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

  # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
  def like(%User{ap_id: ap_id} = user, %Object{data: %{"id" => id}} = object, activity_id \\ nil, local \\ true) do
    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}
    end
  end

  def unlike(%User{} = actor, %Object{} = object) do
    with %Activity{} = activity <- get_existing_like(actor.ap_id, object),
         {:ok, _activity} <- Repo.delete(activity),
         {:ok, object} <- remove_like_from_object(activity, object) do
      {:ok, object}
      else _e -> {:ok, object}
    end
  end

  def announce(%User{ap_id: ap_id} = user, %Object{data: %{"id" => id}} = object, activity_id \\ nil, local \\ true) do
    with announce_data <- make_announce_data(user, object, activity_id),
         {: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
  end

  def follow(follower, followed, activity_id \\ nil, local \\ true) do
    with data <- make_follow_data(follower, followed, activity_id),
         {:ok, activity} <- insert(data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

  def unfollow(follower, followed, local \\ true) do
    with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
         unfollow_data <- make_unfollow_data(follower, followed, follow_activity),
         {:ok, activity} <- insert(unfollow_data, local),
         :ok, maybe_federate(activity) do
      {:ok, activity}
    end
  end

  def fetch_activities_for_context(context) do
    query = from activity in Activity,
      where: fragment("? @> ?", activity.data, ^%{ type: "Create", context: context }),
      order_by: [desc: :inserted_at]
    Repo.all(query)
  end

  def fetch_public_activities(opts \\ %{}) do
    public = ["https://www.w3.org/ns/activitystreams#Public"]
    fetch_activities(public, opts)
  end

  defp restrict_since(query, %{"since_id" => since_id}) do
    from activity in query, where: activity.id > ^since_id
  end
  defp restrict_since(query, _), do: query

  defp restrict_recipients(query, recipients) do
    Enum.reduce(recipients, query, fn (recipient, q) ->
      map = %{ to: [recipient] }
      from activity in q,
      or_where: fragment(~s(? @> ?), activity.data, ^map)
    end)
  end

  defp restrict_local(query, %{"local_only" => true}) do
    from activity in query, where: activity.local == true
  end
  defp restrict_local(query, _), do: query

  defp restrict_max(query, %{"max_id" => max_id}) do
    from activity in query, where: activity.id < ^max_id
  end
  defp restrict_max(query, _), do: query

  defp restrict_actor(query, %{"actor_id" => actor_id}) do
    from activity in query,
      where: fragment("? @> ?", activity.data, ^%{actor: actor_id})
  end
  defp restrict_actor(query, _), do: query

  def fetch_activities(recipients, opts \\ %{}) do
    base_query = from activity in Activity,
      limit: 20,
      order_by: [desc: :inserted_at]

    base_query
    |> restrict_recipients(recipients)
    |> restrict_since(opts)
    |> restrict_local(opts)
    |> restrict_max(opts)
    |> restrict_actor(opts)
    |> Repo.all
    |> Enum.reverse
  end

  def upload(file) do
    data = Upload.store(file)
    Repo.insert(%Object{data: data})
  end
end