user_view.ex 8.82 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 6
defmodule Pleroma.Web.ActivityPub.UserView do
  use Pleroma.Web, :view
7

8
  alias Pleroma.Keys
Haelwenn's avatar
Haelwenn committed
9
  alias Pleroma.Repo
10
  alias Pleroma.User
Haelwenn's avatar
Haelwenn committed
11 12
  alias Pleroma.Web.ActivityPub.Transmogrifier
  alias Pleroma.Web.ActivityPub.Utils
13
  alias Pleroma.Web.Endpoint
14
  alias Pleroma.Web.Router.Helpers
15

16
  import Ecto.Query
lain's avatar
lain committed
17

18 19 20 21 22
  def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do
    %{"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)}
  end

  def render("endpoints.json", %{user: %User{local: true} = _user}) do
23
    %{
24
      "oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize),
25
      "oauthRegistrationEndpoint" => Helpers.app_url(Endpoint, :create),
26
      "oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange),
Haelwenn's avatar
Haelwenn committed
27 28
      "sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox),
      "uploadMedia" => Helpers.activity_pub_url(Endpoint, :upload_media)
29 30 31
    }
  end

32
  def render("endpoints.json", _), do: %{}
33

34
  def render("service.json", %{user: user}) do
35
    {:ok, user} = User.ensure_keys_present(user)
rinpatch's avatar
rinpatch committed
36
    {:ok, _, public_key} = Keys.keys_from_pem(user.keys)
37 38 39
    public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
    public_key = :public_key.pem_encode([public_key])

40 41
    endpoints = render("endpoints.json", %{user: user})

42 43 44
    %{
      "id" => user.ap_id,
      "type" => "Application",
45 46
      "following" => "#{user.ap_id}/following",
      "followers" => "#{user.ap_id}/followers",
47 48
      "inbox" => "#{user.ap_id}/inbox",
      "name" => "Pleroma",
49 50
      "summary" =>
        "An internal service actor for this Pleroma instance.  No user-serviceable parts inside.",
51 52 53 54 55 56 57
      "url" => user.ap_id,
      "manuallyApprovesFollowers" => false,
      "publicKey" => %{
        "id" => "#{user.ap_id}#main-key",
        "owner" => user.ap_id,
        "publicKeyPem" => public_key
      },
58 59
      "endpoints" => endpoints,
      "invisible" => User.invisible?(user)
60
    }
61
    |> Map.merge(Utils.make_json_ld_header())
62 63
  end

64 65 66 67
  def render("users.json", %{users: users}) do
    render_many(users, Pleroma.Web.ActivityPub.UserView, "user.json")
  end

68 69 70 71 72
  # the instance itself is not a Person, but instead an Application
  def render("user.json", %{user: %User{nickname: nil} = user}),
    do: render("service.json", %{user: user})

  def render("user.json", %{user: %User{nickname: "internal." <> _} = user}),
Claire's avatar
Claire committed
73
    do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
74

lain's avatar
lain committed
75
  def render("user.json", %{user: user}) do
76
    {:ok, user} = User.ensure_keys_present(user)
rinpatch's avatar
rinpatch committed
77
    {:ok, _, public_key} = Keys.keys_from_pem(user.keys)
78
    public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
lain's avatar
lain committed
79
    public_key = :public_key.pem_encode([public_key])
lain's avatar
lain committed
80

81 82
    endpoints = render("endpoints.json", %{user: user})

Maksim's avatar
Maksim committed
83
    emoji_tags = Transmogrifier.take_emoji_tags(user)
Haelwenn's avatar
Haelwenn committed
84

85
    fields =
86 87
      user
      |> User.fields()
88 89 90 91 92 93
      |> Enum.map(fn %{"name" => name, "value" => value} ->
        %{
          "name" => Pleroma.HTML.strip_tags(name),
          "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
        }
      end)
94 95
      |> Enum.map(&Map.put(&1, "type", "PropertyValue"))

lain's avatar
lain committed
96
    %{
97
      "id" => user.ap_id,
98
      "type" => user.actor_type,
99 100 101 102 103 104 105 106
      "following" => "#{user.ap_id}/following",
      "followers" => "#{user.ap_id}/followers",
      "inbox" => "#{user.ap_id}/inbox",
      "outbox" => "#{user.ap_id}/outbox",
      "preferredUsername" => user.nickname,
      "name" => user.name,
      "summary" => user.bio,
      "url" => user.ap_id,
107
      "manuallyApprovesFollowers" => user.locked,
108 109 110 111
      "publicKey" => %{
        "id" => "#{user.ap_id}#main-key",
        "owner" => user.ap_id,
        "publicKeyPem" => public_key
lain's avatar
lain committed
112
      },
113
      "endpoints" => endpoints,
114
      "attachment" => fields,
115 116
      "tag" => (user.source_data["tag"] || []) ++ emoji_tags,
      "discoverable" => user.discoverable
lain's avatar
lain committed
117
    }
118 119
    |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
    |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
120 121 122
    |> Map.merge(Utils.make_json_ld_header())
  end

123
  def render("following.json", %{user: user, page: page} = opts) do
124 125
    showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
    showing_count = showing_items || !user.hide_follows_count
126

127 128 129
    query = User.get_friends_query(user)
    query = from(user in query, select: [:ap_id])
    following = Repo.all(query)
kaniini's avatar
kaniini committed
130

131
    total =
132
      if showing_count do
133 134 135 136
        length(following)
      else
        0
      end
lain's avatar
lain committed
137

138
    collection(following, "#{user.ap_id}/following", page, showing_items, total)
139 140 141
    |> Map.merge(Utils.make_json_ld_header())
  end

142
  def render("following.json", %{user: user} = opts) do
143 144
    showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
    showing_count = showing_items || !user.hide_follows_count
145

146 147 148
    query = User.get_friends_query(user)
    query = from(user in query, select: [:ap_id])
    following = Repo.all(query)
kaniini's avatar
kaniini committed
149

150
    total =
151
      if showing_count do
152 153 154 155
        length(following)
      else
        0
      end
lain's avatar
lain committed
156

157 158 159
    %{
      "id" => "#{user.ap_id}/following",
      "type" => "OrderedCollection",
160
      "totalItems" => total,
161
      "first" =>
162
        if showing_items do
163
          collection(following, "#{user.ap_id}/following", 1, !user.hide_follows)
164 165 166
        else
          "#{user.ap_id}/following?page=1"
        end
167 168 169 170
    }
    |> Map.merge(Utils.make_json_ld_header())
  end

171
  def render("followers.json", %{user: user, page: page} = opts) do
172 173
    showing_items = (opts[:for] && opts[:for] == user) || !user.hide_followers
    showing_count = showing_items || !user.hide_followers_count
174

175 176 177
    query = User.get_followers_query(user)
    query = from(user in query, select: [:ap_id])
    followers = Repo.all(query)
kaniini's avatar
kaniini committed
178

179
    total =
180
      if showing_count do
181 182 183 184
        length(followers)
      else
        0
      end
lain's avatar
lain committed
185

186
    collection(followers, "#{user.ap_id}/followers", page, showing_items, total)
187 188 189
    |> Map.merge(Utils.make_json_ld_header())
  end

190
  def render("followers.json", %{user: user} = opts) do
191 192
    showing_items = (opts[:for] && opts[:for] == user) || !user.hide_followers
    showing_count = showing_items || !user.hide_followers_count
193

194 195 196
    query = User.get_followers_query(user)
    query = from(user in query, select: [:ap_id])
    followers = Repo.all(query)
kaniini's avatar
kaniini committed
197

198
    total =
199
      if showing_count do
200 201 202 203
        length(followers)
      else
        0
      end
lain's avatar
lain committed
204

205
    %{
lain's avatar
lain committed
206
      "id" => "#{user.ap_id}/followers",
207
      "type" => "OrderedCollection",
kaniini's avatar
kaniini committed
208
      "first" =>
209 210
        if showing_items do
          collection(followers, "#{user.ap_id}/followers", 1, showing_items, total)
211 212 213
        else
          "#{user.ap_id}/followers?page=1"
        end
214
    }
215
    |> maybe_put_total_items(showing_count, total)
216
    |> Map.merge(Utils.make_json_ld_header())
lain's avatar
lain committed
217
  end
kaniini's avatar
kaniini committed
218

219 220 221 222 223
  def render("activity_collection.json", %{iri: iri}) do
    %{
      "id" => iri,
      "type" => "OrderedCollection",
      "first" => "#{iri}?page=true"
kaniini's avatar
kaniini committed
224
    }
225 226
    |> Map.merge(Utils.make_json_ld_header())
  end
kaniini's avatar
kaniini committed
227

228
  def render("activity_collection_page.json", %{activities: activities, iri: iri}) do
Dee's avatar
Dee committed
229
    # this is sorted chronologically, so first activity is the newest (max)
230 231 232 233
    {max_id, min_id, collection} =
      if length(activities) > 0 do
        {
          Enum.at(activities, 0).id,
Dee's avatar
Dee committed
234
          Enum.at(Enum.reverse(activities), 0).id,
235 236 237 238 239 240 241 242 243 244 245 246
          Enum.map(activities, fn act ->
            {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
            data
          end)
        }
      else
        {
          0,
          0,
          []
        }
      end
kaniini's avatar
kaniini committed
247

248
    %{
249
      "id" => "#{iri}?max_id=#{max_id}&page=true",
kaniini's avatar
kaniini committed
250 251 252
      "type" => "OrderedCollectionPage",
      "partOf" => iri,
      "orderedItems" => collection,
253
      "next" => "#{iri}?max_id=#{min_id}&page=true"
kaniini's avatar
kaniini committed
254
    }
255
    |> Map.merge(Utils.make_json_ld_header())
sxsdv1's avatar
sxsdv1 committed
256 257
  end

258 259 260 261 262 263
  defp maybe_put_total_items(map, false, _total), do: map

  defp maybe_put_total_items(map, true, total) do
    Map.put(map, "totalItems", total)
  end

lain's avatar
lain committed
264
  def collection(collection, iri, page, show_items \\ true, total \\ nil) do
feld's avatar
feld committed
265 266 267
    offset = (page - 1) * 10
    items = Enum.slice(collection, offset, 10)
    items = Enum.map(items, fn user -> user.ap_id end)
lain's avatar
lain committed
268
    total = total || length(collection)
feld's avatar
feld committed
269 270 271 272 273

    map = %{
      "id" => "#{iri}?page=#{page}",
      "type" => "OrderedCollectionPage",
      "partOf" => iri,
lain's avatar
lain committed
274
      "totalItems" => total,
lain's avatar
lain committed
275
      "orderedItems" => if(show_items, do: items, else: [])
feld's avatar
feld committed
276 277
    }

lain's avatar
lain committed
278
    if offset < total do
feld's avatar
feld committed
279
      Map.put(map, "next", "#{iri}?page=#{page + 1}")
href's avatar
href committed
280 281
    else
      map
feld's avatar
feld committed
282 283
    end
  end
284 285 286 287 288 289 290 291 292 293 294 295 296

  defp maybe_make_image(func, key, user) do
    if image = func.(user, no_default: true) do
      %{
        key => %{
          "type" => "Image",
          "url" => image
        }
      }
    else
      %{}
    end
  end
lain's avatar
lain committed
297
end