b363a3dc46e880ef676f9e8523f268edbdb90042
[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.Web.WebFinger
9 alias Pleroma.Web.Salmon
10 alias Pleroma.User
11 alias Pleroma.Repo
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.Transmogrifier
14 alias Pleroma.Web.ActivityPub.Utils
15 alias Pleroma.Web.Router.Helpers
16 alias Pleroma.Web.Endpoint
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 "icon" => %{
91 "type" => "Image",
92 "url" => User.avatar_url(user)
93 },
94 "image" => %{
95 "type" => "Image",
96 "url" => User.banner_url(user)
97 },
98 "tag" => user.info.source_data["tag"] || []
99 }
100 |> Map.merge(Utils.make_json_ld_header())
101 end
102
103 def render("following.json", %{user: user, page: page}) do
104 query = User.get_friends_query(user)
105 query = from(user in query, select: [:ap_id])
106 following = Repo.all(query)
107
108 collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows)
109 |> Map.merge(Utils.make_json_ld_header())
110 end
111
112 def render("following.json", %{user: user}) do
113 query = User.get_friends_query(user)
114 query = from(user in query, select: [:ap_id])
115 following = Repo.all(query)
116
117 %{
118 "id" => "#{user.ap_id}/following",
119 "type" => "OrderedCollection",
120 "totalItems" => length(following),
121 "first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
122 }
123 |> Map.merge(Utils.make_json_ld_header())
124 end
125
126 def render("followers.json", %{user: user, page: page}) do
127 query = User.get_followers_query(user)
128 query = from(user in query, select: [:ap_id])
129 followers = Repo.all(query)
130
131 collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers)
132 |> Map.merge(Utils.make_json_ld_header())
133 end
134
135 def render("followers.json", %{user: user}) do
136 query = User.get_followers_query(user)
137 query = from(user in query, select: [:ap_id])
138 followers = Repo.all(query)
139
140 %{
141 "id" => "#{user.ap_id}/followers",
142 "type" => "OrderedCollection",
143 "totalItems" => length(followers),
144 "first" => collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers)
145 }
146 |> Map.merge(Utils.make_json_ld_header())
147 end
148
149 def render("outbox.json", %{user: user, max_id: max_qid}) do
150 # XXX: technically note_count is wrong for this, but it's better than nothing
151 info = User.user_info(user)
152
153 params = %{
154 "limit" => "10"
155 }
156
157 params =
158 if max_qid != nil do
159 Map.put(params, "max_id", max_qid)
160 else
161 params
162 end
163
164 activities = ActivityPub.fetch_user_activities(user, nil, params)
165 min_id = Enum.at(Enum.reverse(activities), 0).id
166 max_id = Enum.at(activities, 0).id
167
168 collection =
169 Enum.map(activities, fn act ->
170 {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
171 data
172 end)
173
174 iri = "#{user.ap_id}/outbox"
175
176 page = %{
177 "id" => "#{iri}?max_id=#{max_id}",
178 "type" => "OrderedCollectionPage",
179 "partOf" => iri,
180 "totalItems" => info.note_count,
181 "orderedItems" => collection,
182 "next" => "#{iri}?max_id=#{min_id}"
183 }
184
185 if max_qid == nil do
186 %{
187 "id" => iri,
188 "type" => "OrderedCollection",
189 "totalItems" => info.note_count,
190 "first" => page
191 }
192 |> Map.merge(Utils.make_json_ld_header())
193 else
194 page |> Map.merge(Utils.make_json_ld_header())
195 end
196 end
197
198 def render("inbox.json", %{user: user, max_id: max_qid}) do
199 params = %{
200 "limit" => "10"
201 }
202
203 params =
204 if max_qid != nil do
205 Map.put(params, "max_id", max_qid)
206 else
207 params
208 end
209
210 activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
211
212 min_id = Enum.at(Enum.reverse(activities), 0).id
213 max_id = Enum.at(activities, 0).id
214
215 collection =
216 Enum.map(activities, fn act ->
217 {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
218 data
219 end)
220
221 iri = "#{user.ap_id}/inbox"
222
223 page = %{
224 "id" => "#{iri}?max_id=#{max_id}",
225 "type" => "OrderedCollectionPage",
226 "partOf" => iri,
227 "totalItems" => -1,
228 "orderedItems" => collection,
229 "next" => "#{iri}?max_id=#{min_id}"
230 }
231
232 if max_qid == nil do
233 %{
234 "id" => iri,
235 "type" => "OrderedCollection",
236 "totalItems" => -1,
237 "first" => page
238 }
239 |> Map.merge(Utils.make_json_ld_header())
240 else
241 page |> Map.merge(Utils.make_json_ld_header())
242 end
243 end
244
245 def collection(collection, iri, page, show_items \\ true, total \\ nil) do
246 offset = (page - 1) * 10
247 items = Enum.slice(collection, offset, 10)
248 items = Enum.map(items, fn user -> user.ap_id end)
249 total = total || length(collection)
250
251 map = %{
252 "id" => "#{iri}?page=#{page}",
253 "type" => "OrderedCollectionPage",
254 "partOf" => iri,
255 "totalItems" => total,
256 "orderedItems" => if(show_items, do: items, else: [])
257 }
258
259 if offset < total do
260 Map.put(map, "next", "#{iri}?page=#{page + 1}")
261 else
262 map
263 end
264 end
265 end