506fa5ea35346e89d81450017aa6fc28348a8208
[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 "orderedItems" => collection,
181 "next" => "#{iri}?max_id=#{min_id}"
182 }
183
184 if max_qid == nil do
185 %{
186 "id" => iri,
187 "type" => "OrderedCollection",
188 "first" => page
189 }
190 |> Map.merge(Utils.make_json_ld_header())
191 else
192 page |> Map.merge(Utils.make_json_ld_header())
193 end
194 end
195
196 def render("inbox.json", %{user: user, max_id: max_qid}) do
197 params = %{
198 "limit" => "10"
199 }
200
201 params =
202 if max_qid != nil do
203 Map.put(params, "max_id", max_qid)
204 else
205 params
206 end
207
208 activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
209
210 min_id = Enum.at(Enum.reverse(activities), 0).id
211 max_id = Enum.at(activities, 0).id
212
213 collection =
214 Enum.map(activities, fn act ->
215 {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
216 data
217 end)
218
219 iri = "#{user.ap_id}/inbox"
220
221 page = %{
222 "id" => "#{iri}?max_id=#{max_id}",
223 "type" => "OrderedCollectionPage",
224 "partOf" => iri,
225 "orderedItems" => collection,
226 "next" => "#{iri}?max_id=#{min_id}"
227 }
228
229 if max_qid == nil do
230 %{
231 "id" => iri,
232 "type" => "OrderedCollection",
233 "first" => page
234 }
235 |> Map.merge(Utils.make_json_ld_header())
236 else
237 page |> Map.merge(Utils.make_json_ld_header())
238 end
239 end
240
241 def collection(collection, iri, page, show_items \\ true, total \\ nil) do
242 offset = (page - 1) * 10
243 items = Enum.slice(collection, offset, 10)
244 items = Enum.map(items, fn user -> user.ap_id end)
245 total = total || length(collection)
246
247 map = %{
248 "id" => "#{iri}?page=#{page}",
249 "type" => "OrderedCollectionPage",
250 "partOf" => iri,
251 "totalItems" => total,
252 "orderedItems" => if(show_items, do: items, else: [])
253 }
254
255 if offset < total do
256 Map.put(map, "next", "#{iri}?page=#{page + 1}")
257 else
258 map
259 end
260 end
261 end