Merge remote-tracking branch 'pleroma/develop' into feature/disable-account
[akkoma] / lib / pleroma / web / activity_pub / views / user_view.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 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.Repo
9 alias Pleroma.User
10 alias Pleroma.Web.ActivityPub.ActivityPub
11 alias Pleroma.Web.ActivityPub.Transmogrifier
12 alias Pleroma.Web.ActivityPub.Utils
13 alias Pleroma.Web.Endpoint
14 alias Pleroma.Web.Router.Helpers
15 alias Pleroma.Web.Salmon
16 alias Pleroma.Web.WebFinger
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.mastodon_api_url(Endpoint, :create_app),
28 "oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange),
29 "sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)
30 }
31 end
32
33 def render("endpoints.json", _), do: %{}
34
35 # the instance itself is not a Person, but instead an Application
36 def render("user.json", %{user: %{nickname: nil} = user}) do
37 {:ok, user} = WebFinger.ensure_keys_present(user)
38 {:ok, _, public_key} = Salmon.keys_from_pem(user.info.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" => "Virtual actor for Pleroma relay",
52 "url" => user.ap_id,
53 "manuallyApprovesFollowers" => false,
54 "publicKey" => %{
55 "id" => "#{user.ap_id}#main-key",
56 "owner" => user.ap_id,
57 "publicKeyPem" => public_key
58 },
59 "endpoints" => endpoints
60 }
61 |> Map.merge(Utils.make_json_ld_header())
62 end
63
64 def render("user.json", %{user: user}) do
65 {:ok, user} = WebFinger.ensure_keys_present(user)
66 {:ok, _, public_key} = Salmon.keys_from_pem(user.info.keys)
67 public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
68 public_key = :public_key.pem_encode([public_key])
69
70 endpoints = render("endpoints.json", %{user: user})
71
72 %{
73 "id" => user.ap_id,
74 "type" => "Person",
75 "following" => "#{user.ap_id}/following",
76 "followers" => "#{user.ap_id}/followers",
77 "inbox" => "#{user.ap_id}/inbox",
78 "outbox" => "#{user.ap_id}/outbox",
79 "preferredUsername" => user.nickname,
80 "name" => user.name,
81 "summary" => user.bio,
82 "url" => user.ap_id,
83 "manuallyApprovesFollowers" => user.info.locked,
84 "publicKey" => %{
85 "id" => "#{user.ap_id}#main-key",
86 "owner" => user.ap_id,
87 "publicKeyPem" => public_key
88 },
89 "endpoints" => endpoints,
90 "tag" => user.info.source_data["tag"] || []
91 }
92 |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
93 |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
94 |> Map.merge(Utils.make_json_ld_header())
95 end
96
97 def render("following.json", %{user: user, page: page}) do
98 query = User.get_friends_query(user)
99 query = from(user in query, select: [:ap_id])
100 following = Repo.all(query)
101
102 total =
103 if !user.info.hide_follows do
104 length(following)
105 else
106 0
107 end
108
109 collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows, total)
110 |> Map.merge(Utils.make_json_ld_header())
111 end
112
113 def render("following.json", %{user: user}) do
114 query = User.get_friends_query(user)
115 query = from(user in query, select: [:ap_id])
116 following = Repo.all(query)
117
118 total =
119 if !user.info.hide_follows do
120 length(following)
121 else
122 0
123 end
124
125 %{
126 "id" => "#{user.ap_id}/following",
127 "type" => "OrderedCollection",
128 "totalItems" => total,
129 "first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
130 }
131 |> Map.merge(Utils.make_json_ld_header())
132 end
133
134 def render("followers.json", %{user: user, page: page}) do
135 query = User.get_followers_query(user)
136 query = from(user in query, select: [:ap_id])
137 followers = Repo.all(query)
138
139 total =
140 if !user.info.hide_followers do
141 length(followers)
142 else
143 0
144 end
145
146 collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers, total)
147 |> Map.merge(Utils.make_json_ld_header())
148 end
149
150 def render("followers.json", %{user: user}) do
151 query = User.get_followers_query(user)
152 query = from(user in query, select: [:ap_id])
153 followers = Repo.all(query)
154
155 total =
156 if !user.info.hide_followers do
157 length(followers)
158 else
159 0
160 end
161
162 %{
163 "id" => "#{user.ap_id}/followers",
164 "type" => "OrderedCollection",
165 "totalItems" => total,
166 "first" =>
167 collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers, total)
168 }
169 |> Map.merge(Utils.make_json_ld_header())
170 end
171
172 def render("outbox.json", %{user: user, max_id: max_qid}) do
173 params = %{
174 "limit" => "10"
175 }
176
177 params =
178 if max_qid != nil do
179 Map.put(params, "max_id", max_qid)
180 else
181 params
182 end
183
184 activities = ActivityPub.fetch_user_activities(user, nil, params)
185
186 {max_id, min_id, collection} =
187 if length(activities) > 0 do
188 {
189 Enum.at(Enum.reverse(activities), 0).id,
190 Enum.at(activities, 0).id,
191 Enum.map(activities, fn act ->
192 {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
193 data
194 end)
195 }
196 else
197 {
198 0,
199 0,
200 []
201 }
202 end
203
204 iri = "#{user.ap_id}/outbox"
205
206 page = %{
207 "id" => "#{iri}?max_id=#{max_id}",
208 "type" => "OrderedCollectionPage",
209 "partOf" => iri,
210 "orderedItems" => collection,
211 "next" => "#{iri}?max_id=#{min_id}"
212 }
213
214 if max_qid == nil do
215 %{
216 "id" => iri,
217 "type" => "OrderedCollection",
218 "first" => page
219 }
220 |> Map.merge(Utils.make_json_ld_header())
221 else
222 page |> Map.merge(Utils.make_json_ld_header())
223 end
224 end
225
226 def render("inbox.json", %{user: user, max_id: max_qid}) do
227 params = %{
228 "limit" => "10"
229 }
230
231 params =
232 if max_qid != nil do
233 Map.put(params, "max_id", max_qid)
234 else
235 params
236 end
237
238 activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
239
240 min_id = Enum.at(Enum.reverse(activities), 0).id
241 max_id = Enum.at(activities, 0).id
242
243 collection =
244 Enum.map(activities, fn act ->
245 {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
246 data
247 end)
248
249 iri = "#{user.ap_id}/inbox"
250
251 page = %{
252 "id" => "#{iri}?max_id=#{max_id}",
253 "type" => "OrderedCollectionPage",
254 "partOf" => iri,
255 "orderedItems" => collection,
256 "next" => "#{iri}?max_id=#{min_id}"
257 }
258
259 if max_qid == nil do
260 %{
261 "id" => iri,
262 "type" => "OrderedCollection",
263 "first" => page
264 }
265 |> Map.merge(Utils.make_json_ld_header())
266 else
267 page |> Map.merge(Utils.make_json_ld_header())
268 end
269 end
270
271 def collection(collection, iri, page, show_items \\ true, total \\ nil) do
272 offset = (page - 1) * 10
273 items = Enum.slice(collection, offset, 10)
274 items = Enum.map(items, fn user -> user.ap_id end)
275 total = total || length(collection)
276
277 map = %{
278 "id" => "#{iri}?page=#{page}",
279 "type" => "OrderedCollectionPage",
280 "partOf" => iri,
281 "totalItems" => total,
282 "orderedItems" => if(show_items, do: items, else: [])
283 }
284
285 if offset < total do
286 Map.put(map, "next", "#{iri}?page=#{page + 1}")
287 else
288 map
289 end
290 end
291
292 defp maybe_make_image(func, key, user) do
293 if image = func.(user, no_default: true) do
294 %{
295 key => %{
296 "type" => "Image",
297 "url" => image
298 }
299 }
300 else
301 %{}
302 end
303 end
304 end