user_view.ex 8.53 KB
Newer Older
1
# Pleroma: A lightweight social networking server
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 68
  # 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}),
Thibaut Girka's avatar
Thibaut Girka committed
69
    do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
70

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

77 78
    endpoints = render("endpoints.json", %{user: user})

Maksim's avatar
Maksim committed
79
    emoji_tags = Transmogrifier.take_emoji_tags(user)
Haelwenn's avatar
Haelwenn committed
80

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

lain's avatar
lain committed
92
    %{
93
      "id" => user.ap_id,
94
      "type" => user.actor_type,
95 96 97 98 99 100 101 102
      "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,
103
      "manuallyApprovesFollowers" => user.locked,
104 105 106 107
      "publicKey" => %{
        "id" => "#{user.ap_id}#main-key",
        "owner" => user.ap_id,
        "publicKeyPem" => public_key
lain's avatar
lain committed
108
      },
109
      "endpoints" => endpoints,
110
      "attachment" => fields,
111 112
      "tag" => (user.source_data["tag"] || []) ++ emoji_tags,
      "discoverable" => user.discoverable
lain's avatar
lain committed
113
    }
114 115
    |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
    |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
116 117 118
    |> Map.merge(Utils.make_json_ld_header())
  end

119
  def render("following.json", %{user: user, page: page} = opts) do
120 121
    showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
    showing_count = showing_items || !user.hide_follows_count
122

123 124 125
    query = User.get_friends_query(user)
    query = from(user in query, select: [:ap_id])
    following = Repo.all(query)
126

127
    total =
128
      if showing_count do
129 130 131 132
        length(following)
      else
        0
      end
lain's avatar
lain committed
133

134
    collection(following, "#{user.ap_id}/following", page, showing_items, total)
135 136 137
    |> Map.merge(Utils.make_json_ld_header())
  end

138
  def render("following.json", %{user: user} = opts) do
139 140
    showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
    showing_count = showing_items || !user.hide_follows_count
141

142 143 144
    query = User.get_friends_query(user)
    query = from(user in query, select: [:ap_id])
    following = Repo.all(query)
145

146
    total =
147
      if showing_count do
148 149 150 151
        length(following)
      else
        0
      end
lain's avatar
lain committed
152

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

167
  def render("followers.json", %{user: user, page: page} = opts) do
168 169
    showing_items = (opts[:for] && opts[:for] == user) || !user.hide_followers
    showing_count = showing_items || !user.hide_followers_count
170

171 172 173
    query = User.get_followers_query(user)
    query = from(user in query, select: [:ap_id])
    followers = Repo.all(query)
174

175
    total =
176
      if showing_count do
177 178 179 180
        length(followers)
      else
        0
      end
lain's avatar
lain committed
181

182
    collection(followers, "#{user.ap_id}/followers", page, showing_items, total)
183 184 185
    |> Map.merge(Utils.make_json_ld_header())
  end

186
  def render("followers.json", %{user: user} = opts) do
187 188
    showing_items = (opts[:for] && opts[:for] == user) || !user.hide_followers
    showing_count = showing_items || !user.hide_followers_count
189

190 191 192
    query = User.get_followers_query(user)
    query = from(user in query, select: [:ap_id])
    followers = Repo.all(query)
193

194
    total =
195
      if showing_count do
196 197 198 199
        length(followers)
      else
        0
      end
lain's avatar
lain committed
200

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

215 216 217 218 219
  def render("activity_collection.json", %{iri: iri}) do
    %{
      "id" => iri,
      "type" => "OrderedCollection",
      "first" => "#{iri}?page=true"
220
    }
221 222
    |> Map.merge(Utils.make_json_ld_header())
  end
223

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

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

lain's avatar
lain committed
254
  def collection(collection, iri, page, show_items \\ true, total \\ nil) do
feld's avatar
feld committed
255 256 257
    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
258
    total = total || length(collection)
feld's avatar
feld committed
259 260 261 262 263

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

lain's avatar
lain committed
268
    if offset < total do
feld's avatar
feld committed
269
      Map.put(map, "next", "#{iri}?page=#{page + 1}")
href's avatar
href committed
270 271
    else
      map
feld's avatar
feld committed
272 273
    end
  end
274 275 276 277 278 279 280 281 282 283 284 285 286

  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
287
end