Merge remote-tracking branch 'origin/develop' into feature/bbs
[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 user_tags =
73 user
74 |> Transmogrifier.add_emoji_tags()
75 |> Map.get("tag", [])
76
77 %{
78 "id" => user.ap_id,
79 "type" => "Person",
80 "following" => "#{user.ap_id}/following",
81 "followers" => "#{user.ap_id}/followers",
82 "inbox" => "#{user.ap_id}/inbox",
83 "outbox" => "#{user.ap_id}/outbox",
84 "preferredUsername" => user.nickname,
85 "name" => user.name,
86 "summary" => user.bio,
87 "url" => user.ap_id,
88 "manuallyApprovesFollowers" => user.info.locked,
89 "publicKey" => %{
90 "id" => "#{user.ap_id}#main-key",
91 "owner" => user.ap_id,
92 "publicKeyPem" => public_key
93 },
94 "endpoints" => endpoints,
95 "tag" => (user.info.source_data["tag"] || []) ++ user_tags
96 }
97 |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
98 |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
99 |> Map.merge(Utils.make_json_ld_header())
100 end
101
102 def render("following.json", %{user: user, page: page}) do
103 query = User.get_friends_query(user)
104 query = from(user in query, select: [:ap_id])
105 following = Repo.all(query)
106
107 total =
108 if !user.info.hide_follows do
109 length(following)
110 else
111 0
112 end
113
114 collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows, total)
115 |> Map.merge(Utils.make_json_ld_header())
116 end
117
118 def render("following.json", %{user: user}) do
119 query = User.get_friends_query(user)
120 query = from(user in query, select: [:ap_id])
121 following = Repo.all(query)
122
123 total =
124 if !user.info.hide_follows do
125 length(following)
126 else
127 0
128 end
129
130 %{
131 "id" => "#{user.ap_id}/following",
132 "type" => "OrderedCollection",
133 "totalItems" => total,
134 "first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
135 }
136 |> Map.merge(Utils.make_json_ld_header())
137 end
138
139 def render("followers.json", %{user: user, page: page}) do
140 query = User.get_followers_query(user)
141 query = from(user in query, select: [:ap_id])
142 followers = Repo.all(query)
143
144 total =
145 if !user.info.hide_followers do
146 length(followers)
147 else
148 0
149 end
150
151 collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers, total)
152 |> Map.merge(Utils.make_json_ld_header())
153 end
154
155 def render("followers.json", %{user: user}) do
156 query = User.get_followers_query(user)
157 query = from(user in query, select: [:ap_id])
158 followers = Repo.all(query)
159
160 total =
161 if !user.info.hide_followers do
162 length(followers)
163 else
164 0
165 end
166
167 %{
168 "id" => "#{user.ap_id}/followers",
169 "type" => "OrderedCollection",
170 "totalItems" => total,
171 "first" =>
172 collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers, total)
173 }
174 |> Map.merge(Utils.make_json_ld_header())
175 end
176
177 def render("outbox.json", %{user: user, max_id: max_qid}) do
178 params = %{
179 "limit" => "10"
180 }
181
182 params =
183 if max_qid != nil do
184 Map.put(params, "max_id", max_qid)
185 else
186 params
187 end
188
189 activities = ActivityPub.fetch_user_activities(user, nil, params)
190
191 {max_id, min_id, collection} =
192 if length(activities) > 0 do
193 {
194 Enum.at(Enum.reverse(activities), 0).id,
195 Enum.at(activities, 0).id,
196 Enum.map(activities, fn act ->
197 {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
198 data
199 end)
200 }
201 else
202 {
203 0,
204 0,
205 []
206 }
207 end
208
209 iri = "#{user.ap_id}/outbox"
210
211 page = %{
212 "id" => "#{iri}?max_id=#{max_id}",
213 "type" => "OrderedCollectionPage",
214 "partOf" => iri,
215 "orderedItems" => collection,
216 "next" => "#{iri}?max_id=#{min_id}"
217 }
218
219 if max_qid == nil do
220 %{
221 "id" => iri,
222 "type" => "OrderedCollection",
223 "first" => page
224 }
225 |> Map.merge(Utils.make_json_ld_header())
226 else
227 page |> Map.merge(Utils.make_json_ld_header())
228 end
229 end
230
231 def render("inbox.json", %{user: user, max_id: max_qid}) do
232 params = %{
233 "limit" => "10"
234 }
235
236 params =
237 if max_qid != nil do
238 Map.put(params, "max_id", max_qid)
239 else
240 params
241 end
242
243 activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
244
245 min_id = Enum.at(Enum.reverse(activities), 0).id
246 max_id = Enum.at(activities, 0).id
247
248 collection =
249 Enum.map(activities, fn act ->
250 {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
251 data
252 end)
253
254 iri = "#{user.ap_id}/inbox"
255
256 page = %{
257 "id" => "#{iri}?max_id=#{max_id}",
258 "type" => "OrderedCollectionPage",
259 "partOf" => iri,
260 "orderedItems" => collection,
261 "next" => "#{iri}?max_id=#{min_id}"
262 }
263
264 if max_qid == nil do
265 %{
266 "id" => iri,
267 "type" => "OrderedCollection",
268 "first" => page
269 }
270 |> Map.merge(Utils.make_json_ld_header())
271 else
272 page |> Map.merge(Utils.make_json_ld_header())
273 end
274 end
275
276 def collection(collection, iri, page, show_items \\ true, total \\ nil) do
277 offset = (page - 1) * 10
278 items = Enum.slice(collection, offset, 10)
279 items = Enum.map(items, fn user -> user.ap_id end)
280 total = total || length(collection)
281
282 map = %{
283 "id" => "#{iri}?page=#{page}",
284 "type" => "OrderedCollectionPage",
285 "partOf" => iri,
286 "totalItems" => total,
287 "orderedItems" => if(show_items, do: items, else: [])
288 }
289
290 if offset < total do
291 Map.put(map, "next", "#{iri}?page=#{page + 1}")
292 else
293 map
294 end
295 end
296
297 defp maybe_make_image(func, key, user) do
298 if image = func.(user, no_default: true) do
299 %{
300 key => %{
301 "type" => "Image",
302 "url" => image
303 }
304 }
305 else
306 %{}
307 end
308 end
309 end