purge chat and shout endpoints
[akkoma] / lib / pleroma / web / activity_pub / views / user_view.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.UserView do
6 use Pleroma.Web, :view
7
8 alias Pleroma.Keys
9 alias Pleroma.Object
10 alias Pleroma.Repo
11 alias Pleroma.User
12 alias Pleroma.Web.ActivityPub.ObjectView
13 alias Pleroma.Web.ActivityPub.Transmogrifier
14 alias Pleroma.Web.ActivityPub.Utils
15 alias Pleroma.Web.Endpoint
16 alias Pleroma.Web.Router.Helpers
17
18 import Ecto.Query
19
20 def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do
21 %{"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)}
22 end
23
24 def render("endpoints.json", %{user: %User{local: true} = _user}) do
25 %{
26 "oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize),
27 "oauthRegistrationEndpoint" => Helpers.app_url(Endpoint, :create),
28 "oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange),
29 "sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox),
30 "uploadMedia" => Helpers.activity_pub_url(Endpoint, :upload_media)
31 }
32 end
33
34 def render("endpoints.json", _), do: %{}
35
36 def render("service.json", %{user: user}) do
37 {:ok, user} = User.ensure_keys_present(user)
38 {:ok, _, public_key} = Keys.keys_from_pem(user.keys)
39 public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
40 public_key = :public_key.pem_encode([public_key])
41
42 endpoints = render("endpoints.json", %{user: user})
43
44 %{
45 "id" => user.ap_id,
46 "type" => "Application",
47 "following" => "#{user.ap_id}/following",
48 "followers" => "#{user.ap_id}/followers",
49 "inbox" => "#{user.ap_id}/inbox",
50 "name" => "Pleroma",
51 "summary" =>
52 "An internal service actor for this Pleroma instance. No user-serviceable parts inside.",
53 "url" => user.ap_id,
54 "manuallyApprovesFollowers" => false,
55 "publicKey" => %{
56 "id" => "#{user.ap_id}#main-key",
57 "owner" => user.ap_id,
58 "publicKeyPem" => public_key
59 },
60 "endpoints" => endpoints,
61 "invisible" => User.invisible?(user)
62 }
63 |> Map.merge(Utils.make_json_ld_header())
64 end
65
66 # the instance itself is not a Person, but instead an Application
67 def render("user.json", %{user: %User{nickname: nil} = user}),
68 do: render("service.json", %{user: user})
69
70 def render("user.json", %{user: %User{nickname: "internal." <> _} = user}),
71 do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
72
73 def render("user.json", %{user: user}) do
74 {:ok, user} = User.ensure_keys_present(user)
75 {:ok, _, public_key} = Keys.keys_from_pem(user.keys)
76 public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
77 public_key = :public_key.pem_encode([public_key])
78 user = User.sanitize_html(user)
79
80 endpoints = render("endpoints.json", %{user: user})
81
82 emoji_tags = Transmogrifier.take_emoji_tags(user)
83
84 fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
85
86 capabilities = %{}
87
88 %{
89 "id" => user.ap_id,
90 "type" => user.actor_type,
91 "following" => "#{user.ap_id}/following",
92 "followers" => "#{user.ap_id}/followers",
93 "inbox" => "#{user.ap_id}/inbox",
94 "outbox" => "#{user.ap_id}/outbox",
95 "featured" => "#{user.ap_id}/collections/featured",
96 "preferredUsername" => user.nickname,
97 "name" => user.name,
98 "summary" => user.bio,
99 "url" => user.ap_id,
100 "manuallyApprovesFollowers" => user.is_locked,
101 "publicKey" => %{
102 "id" => "#{user.ap_id}#main-key",
103 "owner" => user.ap_id,
104 "publicKeyPem" => public_key
105 },
106 "endpoints" => endpoints,
107 "attachment" => fields,
108 "tag" => emoji_tags,
109 # Note: key name is indeed "discoverable" (not an error)
110 "discoverable" => user.is_discoverable,
111 "capabilities" => capabilities,
112 "alsoKnownAs" => user.also_known_as
113 }
114 |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
115 |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
116 |> Map.merge(Utils.make_json_ld_header())
117 end
118
119 def render("following.json", %{user: user, page: page} = opts) do
120 showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
121 showing_count = showing_items || !user.hide_follows_count
122
123 query = User.get_friends_query(user)
124 query = from(user in query, select: [:ap_id])
125 following = Repo.all(query)
126
127 total =
128 if showing_count do
129 length(following)
130 else
131 0
132 end
133
134 collection(following, "#{user.ap_id}/following", page, showing_items, total)
135 |> Map.merge(Utils.make_json_ld_header())
136 end
137
138 def render("following.json", %{user: user} = opts) do
139 showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
140 showing_count = showing_items || !user.hide_follows_count
141
142 query = User.get_friends_query(user)
143 query = from(user in query, select: [:ap_id])
144 following = Repo.all(query)
145
146 total =
147 if showing_count do
148 length(following)
149 else
150 0
151 end
152
153 %{
154 "id" => "#{user.ap_id}/following",
155 "type" => "OrderedCollection",
156 "totalItems" => total,
157 "first" =>
158 if showing_items do
159 collection(following, "#{user.ap_id}/following", 1, !user.hide_follows)
160 else
161 "#{user.ap_id}/following?page=1"
162 end
163 }
164 |> Map.merge(Utils.make_json_ld_header())
165 end
166
167 def render("followers.json", %{user: user, page: page} = opts) do
168 showing_items = (opts[:for] && opts[:for] == user) || !user.hide_followers
169 showing_count = showing_items || !user.hide_followers_count
170
171 query = User.get_followers_query(user)
172 query = from(user in query, select: [:ap_id])
173 followers = Repo.all(query)
174
175 total =
176 if showing_count do
177 length(followers)
178 else
179 0
180 end
181
182 collection(followers, "#{user.ap_id}/followers", page, showing_items, total)
183 |> Map.merge(Utils.make_json_ld_header())
184 end
185
186 def render("followers.json", %{user: user} = opts) do
187 showing_items = (opts[:for] && opts[:for] == user) || !user.hide_followers
188 showing_count = showing_items || !user.hide_followers_count
189
190 query = User.get_followers_query(user)
191 query = from(user in query, select: [:ap_id])
192 followers = Repo.all(query)
193
194 total =
195 if showing_count do
196 length(followers)
197 else
198 0
199 end
200
201 %{
202 "id" => "#{user.ap_id}/followers",
203 "type" => "OrderedCollection",
204 "first" =>
205 if showing_items do
206 collection(followers, "#{user.ap_id}/followers", 1, showing_items, total)
207 else
208 "#{user.ap_id}/followers?page=1"
209 end
210 }
211 |> maybe_put_total_items(showing_count, total)
212 |> Map.merge(Utils.make_json_ld_header())
213 end
214
215 def render("activity_collection.json", %{iri: iri}) do
216 %{
217 "id" => iri,
218 "type" => "OrderedCollection",
219 "first" => "#{iri}?page=true"
220 }
221 |> Map.merge(Utils.make_json_ld_header())
222 end
223
224 def render("activity_collection_page.json", %{
225 activities: activities,
226 iri: iri,
227 pagination: pagination
228 }) do
229 collection =
230 Enum.map(activities, fn activity ->
231 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
232 data
233 end)
234
235 %{
236 "type" => "OrderedCollectionPage",
237 "partOf" => iri,
238 "orderedItems" => collection
239 }
240 |> Map.merge(Utils.make_json_ld_header())
241 |> Map.merge(pagination)
242 end
243
244 def render("featured.json", %{
245 user: %{featured_address: featured_address, pinned_objects: pinned_objects}
246 }) do
247 objects =
248 pinned_objects
249 |> Enum.sort_by(fn {_, pinned_at} -> pinned_at end, &>=/2)
250 |> Enum.map(fn {id, _} ->
251 ObjectView.render("object.json", %{object: Object.get_cached_by_ap_id(id)})
252 end)
253
254 %{
255 "id" => featured_address,
256 "type" => "OrderedCollection",
257 "orderedItems" => objects,
258 "totalItems" => length(objects)
259 }
260 |> Map.merge(Utils.make_json_ld_header())
261 end
262
263 defp maybe_put_total_items(map, false, _total), do: map
264
265 defp maybe_put_total_items(map, true, total) do
266 Map.put(map, "totalItems", total)
267 end
268
269 def collection(collection, iri, page, show_items \\ true, total \\ nil) do
270 offset = (page - 1) * 10
271 items = Enum.slice(collection, offset, 10)
272 items = Enum.map(items, fn user -> user.ap_id end)
273 total = total || length(collection)
274
275 map = %{
276 "id" => "#{iri}?page=#{page}",
277 "type" => "OrderedCollectionPage",
278 "partOf" => iri,
279 "totalItems" => total,
280 "orderedItems" => if(show_items, do: items, else: [])
281 }
282
283 if offset < total do
284 Map.put(map, "next", "#{iri}?page=#{page + 1}")
285 else
286 map
287 end
288 end
289
290 defp maybe_make_image(func, key, user) do
291 if image = func.(user, no_default: true) do
292 %{
293 key => %{
294 "type" => "Image",
295 "url" => image
296 }
297 }
298 else
299 %{}
300 end
301 end
302 end