cc3e3582f6de2b8695df8c80425f7ecf57cd91af
[anni] / lib / pleroma / web / mastodon_api / views / account_view.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.MastodonAPI.AccountView do
6   use Pleroma.Web, :view
7
8   alias Pleroma.FollowingRelationship
9   alias Pleroma.User
10   alias Pleroma.UserNote
11   alias Pleroma.UserRelationship
12   alias Pleroma.Web.CommonAPI.Utils
13   alias Pleroma.Web.MastodonAPI.AccountView
14   alias Pleroma.Web.MediaProxy
15
16   def render("index.json", %{users: users} = opts) do
17     reading_user = opts[:for]
18
19     relationships_opt =
20       cond do
21         Map.has_key?(opts, :relationships) ->
22           opts[:relationships]
23
24         is_nil(reading_user) || !opts[:embed_relationships] ->
25           UserRelationship.view_relationships_option(nil, [])
26
27         true ->
28           UserRelationship.view_relationships_option(reading_user, users)
29       end
30
31     opts =
32       opts
33       |> Map.merge(%{relationships: relationships_opt, as: :user})
34       |> Map.delete(:users)
35
36     users
37     |> render_many(AccountView, "show.json", opts)
38     |> Enum.filter(&Enum.any?/1)
39   end
40
41   @doc """
42   Renders specified user account.
43     :skip_visibility_check option skips visibility check and renders any user (local or remote)
44       regardless of [:pleroma, :restrict_unauthenticated] setting.
45     :for option specifies the requester and can be a User record or nil.
46       Only use `user: user, for: user` when `user` is the actual requester of own profile.
47   """
48   def render("show.json", %{user: _user, skip_visibility_check: true} = opts) do
49     do_render("show.json", opts)
50   end
51
52   def render("show.json", %{user: user, for: for_user_or_nil} = opts) do
53     if User.visible_for(user, for_user_or_nil) == :visible do
54       do_render("show.json", opts)
55     else
56       %{}
57     end
58   end
59
60   def render("show.json", _) do
61     raise "In order to prevent account accessibility issues, " <>
62             ":skip_visibility_check or :for option is required."
63   end
64
65   def render("mention.json", %{user: user}) do
66     %{
67       id: to_string(user.id),
68       acct: user.nickname,
69       username: username_from_nickname(user.nickname),
70       url: user.uri || user.ap_id
71     }
72   end
73
74   def render("relationship.json", %{user: nil, target: _target}) do
75     %{}
76   end
77
78   def render(
79         "relationship.json",
80         %{user: %User{} = reading_user, target: %User{} = target} = opts
81       ) do
82     user_relationships = get_in(opts, [:relationships, :user_relationships])
83     following_relationships = get_in(opts, [:relationships, :following_relationships])
84
85     follow_state =
86       if following_relationships do
87         user_to_target_following_relation =
88           FollowingRelationship.find(following_relationships, reading_user, target)
89
90         User.get_follow_state(reading_user, target, user_to_target_following_relation)
91       else
92         User.get_follow_state(reading_user, target)
93       end
94
95     followed_by =
96       if following_relationships do
97         case FollowingRelationship.find(following_relationships, target, reading_user) do
98           %{state: :follow_accept} -> true
99           _ -> false
100         end
101       else
102         User.following?(target, reading_user)
103       end
104
105     subscribing =
106       UserRelationship.exists?(
107         user_relationships,
108         :inverse_subscription,
109         target,
110         reading_user,
111         &User.subscribed_to?(&2, &1)
112       )
113
114     # NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
115     %{
116       id: to_string(target.id),
117       following: follow_state == :follow_accept,
118       followed_by: followed_by,
119       blocking:
120         UserRelationship.exists?(
121           user_relationships,
122           :block,
123           reading_user,
124           target,
125           &User.blocks_user?(&1, &2)
126         ),
127       blocked_by:
128         UserRelationship.exists?(
129           user_relationships,
130           :block,
131           target,
132           reading_user,
133           &User.blocks_user?(&1, &2)
134         ),
135       muting:
136         UserRelationship.exists?(
137           user_relationships,
138           :mute,
139           reading_user,
140           target,
141           &User.mutes?(&1, &2)
142         ),
143       muting_notifications:
144         UserRelationship.exists?(
145           user_relationships,
146           :notification_mute,
147           reading_user,
148           target,
149           &User.muted_notifications?(&1, &2)
150         ),
151       subscribing: subscribing,
152       notifying: subscribing,
153       requested: follow_state == :follow_pending,
154       domain_blocking: User.blocks_domain?(reading_user, target),
155       showing_reblogs:
156         not UserRelationship.exists?(
157           user_relationships,
158           :reblog_mute,
159           reading_user,
160           target,
161           &User.muting_reblogs?(&1, &2)
162         ),
163       note:
164         UserNote.show(
165           reading_user,
166           target
167         ),
168       endorsed:
169         UserRelationship.exists?(
170           user_relationships,
171           :endorsement,
172           target,
173           reading_user,
174           &User.endorses?(&2, &1)
175         )
176     }
177   end
178
179   def render("relationships.json", %{user: user, targets: targets} = opts) do
180     relationships_opt =
181       cond do
182         Map.has_key?(opts, :relationships) ->
183           opts[:relationships]
184
185         is_nil(user) ->
186           UserRelationship.view_relationships_option(nil, [])
187
188         true ->
189           UserRelationship.view_relationships_option(user, targets)
190       end
191
192     render_opts = %{as: :target, user: user, relationships: relationships_opt}
193     render_many(targets, AccountView, "relationship.json", render_opts)
194   end
195
196   defp do_render("show.json", %{user: user} = opts) do
197     user = User.sanitize_html(user, User.html_filter_policy(opts[:for]))
198     display_name = user.name || user.nickname
199
200     avatar = User.avatar_url(user) |> MediaProxy.url()
201     avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(static: true)
202     header = User.banner_url(user) |> MediaProxy.url()
203     header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true)
204
205     following_count =
206       if !user.hide_follows_count or !user.hide_follows or opts[:for] == user,
207         do: user.following_count,
208         else: 0
209
210     followers_count =
211       if !user.hide_followers_count or !user.hide_followers or opts[:for] == user,
212         do: user.follower_count,
213         else: 0
214
215     bot = user.actor_type == "Service"
216
217     emojis =
218       Enum.map(user.emoji, fn {shortcode, raw_url} ->
219         url = MediaProxy.url(raw_url)
220
221         %{
222           shortcode: shortcode,
223           url: url,
224           static_url: url,
225           visible_in_picker: false
226         }
227       end)
228
229     relationship =
230       if opts[:embed_relationships] do
231         render("relationship.json", %{
232           user: opts[:for],
233           target: user,
234           relationships: opts[:relationships]
235         })
236       else
237         %{}
238       end
239
240     favicon =
241       if Pleroma.Config.get([:instances_favicons, :enabled]) do
242         user
243         |> Map.get(:ap_id, "")
244         |> URI.parse()
245         |> URI.merge("/")
246         |> Pleroma.Instances.Instance.get_or_update_favicon()
247         |> MediaProxy.url()
248       else
249         nil
250       end
251
252     %{
253       id: to_string(user.id),
254       username: username_from_nickname(user.nickname),
255       acct: user.nickname,
256       display_name: display_name,
257       locked: user.is_locked,
258       created_at: Utils.to_masto_date(user.inserted_at),
259       followers_count: followers_count,
260       following_count: following_count,
261       statuses_count: user.note_count,
262       note: user.bio,
263       url: user.uri || user.ap_id,
264       avatar: avatar,
265       avatar_static: avatar_static,
266       header: header,
267       header_static: header_static,
268       emojis: emojis,
269       fields: user.fields,
270       bot: bot,
271       source: %{
272         note: user.raw_bio || "",
273         sensitive: false,
274         fields: user.raw_fields,
275         pleroma: %{
276           discoverable: user.is_discoverable,
277           actor_type: user.actor_type
278         }
279       },
280       last_status_at: user.last_status_at,
281
282       # Pleroma extensions
283       # Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub
284       fqn: User.full_nickname(user),
285       pleroma: %{
286         ap_id: user.ap_id,
287         also_known_as: user.also_known_as,
288         is_confirmed: user.is_confirmed,
289         is_suggested: user.is_suggested,
290         tags: user.tags,
291         hide_followers_count: user.hide_followers_count,
292         hide_follows_count: user.hide_follows_count,
293         hide_followers: user.hide_followers,
294         hide_follows: user.hide_follows,
295         hide_favorites: user.hide_favorites,
296         relationship: relationship,
297         skip_thread_containment: user.skip_thread_containment,
298         background_image: image_url(user.background) |> MediaProxy.url(),
299         accepts_chat_messages: user.accepts_chat_messages,
300         favicon: favicon
301       }
302     }
303     |> maybe_put_role(user, opts[:for])
304     |> maybe_put_settings(user, opts[:for], opts)
305     |> maybe_put_notification_settings(user, opts[:for])
306     |> maybe_put_settings_store(user, opts[:for], opts)
307     |> maybe_put_chat_token(user, opts[:for], opts)
308     |> maybe_put_activation_status(user, opts[:for])
309     |> maybe_put_follow_requests_count(user, opts[:for])
310     |> maybe_put_allow_following_move(user, opts[:for])
311     |> maybe_put_unread_conversation_count(user, opts[:for])
312     |> maybe_put_unread_notification_count(user, opts[:for])
313     |> maybe_put_email_address(user, opts[:for])
314     |> maybe_put_mute_expires_at(user, opts[:for], opts)
315     |> maybe_show_birthday(user, opts[:for])
316   end
317
318   defp username_from_nickname(string) when is_binary(string) do
319     hd(String.split(string, "@"))
320   end
321
322   defp username_from_nickname(_), do: nil
323
324   defp maybe_put_follow_requests_count(
325          data,
326          %User{id: user_id} = user,
327          %User{id: user_id}
328        ) do
329     count =
330       User.get_follow_requests(user)
331       |> length()
332
333     data
334     |> Kernel.put_in([:follow_requests_count], count)
335   end
336
337   defp maybe_put_follow_requests_count(data, _, _), do: data
338
339   defp maybe_put_settings(
340          data,
341          %User{id: user_id} = user,
342          %User{id: user_id},
343          _opts
344        ) do
345     data
346     |> Kernel.put_in([:source, :privacy], user.default_scope)
347     |> Kernel.put_in([:source, :pleroma, :show_role], user.show_role)
348     |> Kernel.put_in([:source, :pleroma, :no_rich_text], user.no_rich_text)
349     |> Kernel.put_in([:source, :pleroma, :show_birthday], user.show_birthday)
350   end
351
352   defp maybe_put_settings(data, _, _, _), do: data
353
354   defp maybe_put_settings_store(data, %User{} = user, %User{}, %{
355          with_pleroma_settings: true
356        }) do
357     data
358     |> Kernel.put_in([:pleroma, :settings_store], user.pleroma_settings_store)
359   end
360
361   defp maybe_put_settings_store(data, _, _, _), do: data
362
363   defp maybe_put_chat_token(data, %User{id: id}, %User{id: id}, %{
364          with_chat_token: token
365        }) do
366     data
367     |> Kernel.put_in([:pleroma, :chat_token], token)
368   end
369
370   defp maybe_put_chat_token(data, _, _, _), do: data
371
372   defp maybe_put_role(data, %User{show_role: true} = user, _) do
373     put_role(data, user)
374   end
375
376   defp maybe_put_role(data, %User{id: user_id} = user, %User{id: user_id}) do
377     put_role(data, user)
378   end
379
380   defp maybe_put_role(data, _, _), do: data
381
382   defp put_role(data, user) do
383     data
384     |> Kernel.put_in([:pleroma, :is_admin], user.is_admin)
385     |> Kernel.put_in([:pleroma, :is_moderator], user.is_moderator)
386     |> Kernel.put_in([:pleroma, :privileges], User.privileges(user))
387   end
388
389   defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
390     Kernel.put_in(
391       data,
392       [:pleroma, :notification_settings],
393       Map.from_struct(user.notification_settings)
394     )
395   end
396
397   defp maybe_put_notification_settings(data, _, _), do: data
398
399   defp maybe_put_allow_following_move(data, %User{id: user_id} = user, %User{id: user_id}) do
400     Kernel.put_in(data, [:pleroma, :allow_following_move], user.allow_following_move)
401   end
402
403   defp maybe_put_allow_following_move(data, _, _), do: data
404
405   defp maybe_put_activation_status(data, user, user_for) do
406     if User.privileged?(user_for, :users_manage_activation_state),
407       do: Kernel.put_in(data, [:pleroma, :deactivated], !user.is_active),
408       else: data
409   end
410
411   defp maybe_put_unread_conversation_count(data, %User{id: user_id} = user, %User{id: user_id}) do
412     data
413     |> Kernel.put_in(
414       [:pleroma, :unread_conversation_count],
415       Pleroma.Conversation.Participation.unread_count(user)
416     )
417   end
418
419   defp maybe_put_unread_conversation_count(data, _, _), do: data
420
421   defp maybe_put_unread_notification_count(data, %User{id: user_id}, %User{id: user_id} = user) do
422     Kernel.put_in(
423       data,
424       [:pleroma, :unread_notifications_count],
425       Pleroma.Notification.unread_notifications_count(user)
426     )
427   end
428
429   defp maybe_put_unread_notification_count(data, _, _), do: data
430
431   defp maybe_put_email_address(data, %User{id: user_id}, %User{id: user_id} = user) do
432     Kernel.put_in(
433       data,
434       [:pleroma, :email],
435       user.email
436     )
437   end
438
439   defp maybe_put_email_address(data, _, _), do: data
440
441   defp maybe_put_mute_expires_at(data, %User{} = user, target, %{mutes: true}) do
442     Map.put(
443       data,
444       :mute_expires_at,
445       UserRelationship.get_mute_expire_date(target, user)
446     )
447   end
448
449   defp maybe_put_mute_expires_at(data, _, _, _), do: data
450
451   defp maybe_show_birthday(data, %User{id: user_id} = user, %User{id: user_id}) do
452     data
453     |> Kernel.put_in([:pleroma, :birthday], user.birthday)
454   end
455
456   defp maybe_show_birthday(data, %User{show_birthday: true} = user, _) do
457     data
458     |> Kernel.put_in([:pleroma, :birthday], user.birthday)
459   end
460
461   defp maybe_show_birthday(data, _, _) do
462     data
463   end
464
465   defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
466   defp image_url(_), do: nil
467 end