Joshua Goins
c22ecac567
Implements the preferences endpoint in the Mastodon API, but returns default values for most of the preferences right now. The only supported preference we can access is default post visibility, and a relevant test is added as well.
443 lines
13 KiB
Elixir
443 lines
13 KiB
Elixir
# Pleroma: A lightweight social networking server
|
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|
use Pleroma.Web, :view
|
|
|
|
alias Pleroma.FollowingRelationship
|
|
alias Pleroma.User
|
|
alias Pleroma.UserNote
|
|
alias Pleroma.UserRelationship
|
|
alias Pleroma.Web.CommonAPI.Utils
|
|
alias Pleroma.Web.MastodonAPI.AccountView
|
|
alias Pleroma.Web.MediaProxy
|
|
|
|
def render("index.json", %{users: users} = opts) do
|
|
reading_user = opts[:for]
|
|
|
|
relationships_opt =
|
|
cond do
|
|
Map.has_key?(opts, :relationships) ->
|
|
opts[:relationships]
|
|
|
|
is_nil(reading_user) || !opts[:embed_relationships] ->
|
|
UserRelationship.view_relationships_option(nil, [])
|
|
|
|
true ->
|
|
UserRelationship.view_relationships_option(reading_user, users)
|
|
end
|
|
|
|
opts =
|
|
opts
|
|
|> Map.merge(%{relationships: relationships_opt, as: :user})
|
|
|> Map.delete(:users)
|
|
|
|
users
|
|
|> render_many(AccountView, "show.json", opts)
|
|
|> Enum.filter(&Enum.any?/1)
|
|
end
|
|
|
|
@doc """
|
|
Renders specified user account.
|
|
:skip_visibility_check option skips visibility check and renders any user (local or remote)
|
|
regardless of [:pleroma, :restrict_unauthenticated] setting.
|
|
:for option specifies the requester and can be a User record or nil.
|
|
Only use `user: user, for: user` when `user` is the actual requester of own profile.
|
|
"""
|
|
def render("show.json", %{user: _user, skip_visibility_check: true} = opts) do
|
|
do_render("show.json", opts)
|
|
end
|
|
|
|
def render("show.json", %{user: user, for: for_user_or_nil} = opts) do
|
|
if User.visible_for(user, for_user_or_nil) == :visible do
|
|
do_render("show.json", opts)
|
|
else
|
|
%{}
|
|
end
|
|
end
|
|
|
|
def render("show.json", _) do
|
|
raise "In order to prevent account accessibility issues, " <>
|
|
":skip_visibility_check or :for option is required."
|
|
end
|
|
|
|
def render("mention.json", %{user: user}) do
|
|
%{
|
|
id: to_string(user.id),
|
|
acct: user.nickname,
|
|
username: username_from_nickname(user.nickname),
|
|
url: user.uri || user.ap_id
|
|
}
|
|
end
|
|
|
|
def render("relationship.json", %{user: nil, target: _target}) do
|
|
%{}
|
|
end
|
|
|
|
def render(
|
|
"relationship.json",
|
|
%{user: %User{} = reading_user, target: %User{} = target} = opts
|
|
) do
|
|
user_relationships = get_in(opts, [:relationships, :user_relationships])
|
|
following_relationships = get_in(opts, [:relationships, :following_relationships])
|
|
|
|
follow_state =
|
|
if following_relationships do
|
|
user_to_target_following_relation =
|
|
FollowingRelationship.find(following_relationships, reading_user, target)
|
|
|
|
User.get_follow_state(reading_user, target, user_to_target_following_relation)
|
|
else
|
|
User.get_follow_state(reading_user, target)
|
|
end
|
|
|
|
followed_by =
|
|
if following_relationships do
|
|
target_to_user_following_relation =
|
|
FollowingRelationship.find(following_relationships, target, reading_user)
|
|
|
|
User.get_follow_state(target, reading_user, target_to_user_following_relation)
|
|
else
|
|
User.get_follow_state(target, reading_user)
|
|
end
|
|
|
|
subscribing =
|
|
UserRelationship.exists?(
|
|
user_relationships,
|
|
:inverse_subscription,
|
|
target,
|
|
reading_user,
|
|
&User.subscribed_to?(&2, &1)
|
|
)
|
|
|
|
# NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
|
|
%{
|
|
id: to_string(target.id),
|
|
following: follow_state == :follow_accept,
|
|
followed_by: followed_by == :follow_accept,
|
|
blocking:
|
|
UserRelationship.exists?(
|
|
user_relationships,
|
|
:block,
|
|
reading_user,
|
|
target,
|
|
&User.blocks_user?(&1, &2)
|
|
),
|
|
blocked_by: false,
|
|
muting:
|
|
UserRelationship.exists?(
|
|
user_relationships,
|
|
:mute,
|
|
reading_user,
|
|
target,
|
|
&User.mutes?(&1, &2)
|
|
),
|
|
muting_notifications:
|
|
UserRelationship.exists?(
|
|
user_relationships,
|
|
:notification_mute,
|
|
reading_user,
|
|
target,
|
|
&User.muted_notifications?(&1, &2)
|
|
),
|
|
subscribing: subscribing,
|
|
notifying: subscribing,
|
|
requested: follow_state == :follow_pending,
|
|
requested_by: followed_by == :follow_pending,
|
|
domain_blocking: User.blocks_domain?(reading_user, target),
|
|
showing_reblogs:
|
|
not UserRelationship.exists?(
|
|
user_relationships,
|
|
:reblog_mute,
|
|
reading_user,
|
|
target,
|
|
&User.muting_reblogs?(&1, &2)
|
|
),
|
|
endorsed: false,
|
|
note:
|
|
UserNote.show(
|
|
reading_user,
|
|
target
|
|
)
|
|
}
|
|
end
|
|
|
|
def render("relationships.json", %{user: user, targets: targets} = opts) do
|
|
relationships_opt =
|
|
cond do
|
|
Map.has_key?(opts, :relationships) ->
|
|
opts[:relationships]
|
|
|
|
is_nil(user) ->
|
|
UserRelationship.view_relationships_option(nil, [])
|
|
|
|
true ->
|
|
UserRelationship.view_relationships_option(user, targets)
|
|
end
|
|
|
|
render_opts = %{as: :target, user: user, relationships: relationships_opt}
|
|
render_many(targets, AccountView, "relationship.json", render_opts)
|
|
end
|
|
|
|
def render("instance.json", %{instance: %Pleroma.Instances.Instance{} = instance}) do
|
|
%{
|
|
name: instance.host,
|
|
favicon: instance.favicon |> MediaProxy.url(),
|
|
nodeinfo: instance.nodeinfo
|
|
}
|
|
end
|
|
|
|
def render("instance.json", _), do: nil
|
|
|
|
defp do_render("show.json", %{user: user} = opts) do
|
|
user = User.sanitize_html(user, User.html_filter_policy(opts[:for]))
|
|
display_name = user.name || user.nickname
|
|
|
|
avatar = User.avatar_url(user) |> MediaProxy.url()
|
|
avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(static: true)
|
|
header = User.banner_url(user) |> MediaProxy.url()
|
|
header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true)
|
|
|
|
following_count =
|
|
if !user.hide_follows_count or !user.hide_follows or opts[:for] == user,
|
|
do: user.following_count,
|
|
else: 0
|
|
|
|
followers_count =
|
|
if !user.hide_followers_count or !user.hide_followers or opts[:for] == user,
|
|
do: user.follower_count,
|
|
else: 0
|
|
|
|
bot = user.actor_type == "Service"
|
|
|
|
emojis =
|
|
Enum.map(user.emoji, fn {shortcode, raw_url} ->
|
|
url = MediaProxy.url(raw_url)
|
|
|
|
%{
|
|
shortcode: shortcode,
|
|
url: url,
|
|
static_url: url,
|
|
visible_in_picker: false
|
|
}
|
|
end)
|
|
|
|
relationship =
|
|
if opts[:embed_relationships] do
|
|
render("relationship.json", %{
|
|
user: opts[:for],
|
|
target: user,
|
|
relationships: opts[:relationships]
|
|
})
|
|
else
|
|
%{}
|
|
end
|
|
|
|
instance =
|
|
with {:ok, instance} <- Pleroma.Instances.Instance.get_cached_by_url(user.ap_id) do
|
|
instance
|
|
else
|
|
_ ->
|
|
nil
|
|
end
|
|
|
|
favicon =
|
|
if is_nil(instance) do
|
|
nil
|
|
else
|
|
instance.favicon
|
|
|> MediaProxy.url()
|
|
end
|
|
|
|
%{
|
|
id: to_string(user.id),
|
|
username: username_from_nickname(user.nickname),
|
|
acct: user.nickname,
|
|
display_name: display_name,
|
|
locked: user.is_locked,
|
|
created_at: Utils.to_masto_date(user.inserted_at),
|
|
followers_count: followers_count,
|
|
following_count: following_count,
|
|
statuses_count: user.note_count,
|
|
note: user.bio,
|
|
url: user.uri || user.ap_id,
|
|
avatar: avatar,
|
|
avatar_static: avatar_static,
|
|
header: header,
|
|
header_static: header_static,
|
|
emojis: emojis,
|
|
fields: user.fields,
|
|
bot: bot,
|
|
source: %{
|
|
note: user.raw_bio || "",
|
|
sensitive: false,
|
|
fields: user.raw_fields,
|
|
pleroma: %{
|
|
discoverable: user.is_discoverable,
|
|
actor_type: user.actor_type
|
|
}
|
|
},
|
|
last_status_at: user.last_status_at,
|
|
akkoma: %{
|
|
instance: render("instance.json", %{instance: instance}),
|
|
status_ttl_days: user.status_ttl_days
|
|
},
|
|
# Pleroma extensions
|
|
# Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub
|
|
fqn: User.full_nickname(user),
|
|
pleroma: %{
|
|
ap_id: user.ap_id,
|
|
also_known_as: user.also_known_as,
|
|
is_confirmed: user.is_confirmed,
|
|
is_suggested: user.is_suggested,
|
|
tags: user.tags,
|
|
hide_followers_count: user.hide_followers_count,
|
|
hide_follows_count: user.hide_follows_count,
|
|
hide_followers: user.hide_followers,
|
|
hide_follows: user.hide_follows,
|
|
hide_favorites: user.hide_favorites,
|
|
relationship: relationship,
|
|
skip_thread_containment: user.skip_thread_containment,
|
|
background_image: image_url(user.background) |> MediaProxy.url(),
|
|
favicon: favicon
|
|
}
|
|
}
|
|
|> maybe_put_role(user, opts[:for])
|
|
|> maybe_put_settings(user, opts[:for], opts)
|
|
|> maybe_put_notification_settings(user, opts[:for])
|
|
|> maybe_put_settings_store(user, opts[:for], opts)
|
|
|> maybe_put_activation_status(user, opts[:for])
|
|
|> maybe_put_follow_requests_count(user, opts[:for])
|
|
|> maybe_put_allow_following_move(user, opts[:for])
|
|
|> maybe_put_unread_conversation_count(user, opts[:for])
|
|
|> maybe_put_unread_notification_count(user, opts[:for])
|
|
|> maybe_put_email_address(user, opts[:for])
|
|
end
|
|
|
|
def render("preferences.json", %{user: user} = opts) do
|
|
# TODO: Do we expose more settings that make sense to plug in here?
|
|
%{
|
|
"posting:default:visibility": user.default_scope,
|
|
"posting:default:sensitive": false,
|
|
"posting:default:language": nil,
|
|
"reading:expand:media": "default",
|
|
"reading:expand:spoilers": false
|
|
}
|
|
end
|
|
|
|
defp username_from_nickname(string) when is_binary(string) do
|
|
hd(String.split(string, "@"))
|
|
end
|
|
|
|
defp username_from_nickname(_), do: nil
|
|
|
|
defp maybe_put_follow_requests_count(
|
|
data,
|
|
%User{id: user_id} = user,
|
|
%User{id: user_id}
|
|
) do
|
|
count =
|
|
user
|
|
|> User.get_follow_requests()
|
|
|> length()
|
|
|
|
data
|
|
|> Kernel.put_in([:follow_requests_count], count)
|
|
end
|
|
|
|
defp maybe_put_follow_requests_count(data, _, _), do: data
|
|
|
|
defp maybe_put_settings(
|
|
data,
|
|
%User{id: user_id} = user,
|
|
%User{id: user_id},
|
|
_opts
|
|
) do
|
|
data
|
|
|> Kernel.put_in([:source, :privacy], user.default_scope)
|
|
|> Kernel.put_in([:source, :pleroma, :show_role], user.show_role)
|
|
|> Kernel.put_in([:source, :pleroma, :no_rich_text], user.no_rich_text)
|
|
|> Kernel.put_in([:accepts_direct_messages_from], user.accepts_direct_messages_from)
|
|
end
|
|
|
|
defp maybe_put_settings(data, _, _, _), do: data
|
|
|
|
defp maybe_put_settings_store(data, %User{} = user, %User{}, %{
|
|
with_pleroma_settings: true
|
|
}) do
|
|
data
|
|
|> Kernel.put_in([:pleroma, :settings_store], user.pleroma_settings_store)
|
|
end
|
|
|
|
defp maybe_put_settings_store(data, _, _, _), do: data
|
|
|
|
defp maybe_put_role(data, %User{show_role: true} = user, _) do
|
|
data
|
|
|> Kernel.put_in([:pleroma, :is_admin], user.is_admin)
|
|
|> Kernel.put_in([:pleroma, :is_moderator], user.is_moderator)
|
|
end
|
|
|
|
defp maybe_put_role(data, %User{id: user_id} = user, %User{id: user_id}) do
|
|
data
|
|
|> Kernel.put_in([:pleroma, :is_admin], user.is_admin)
|
|
|> Kernel.put_in([:pleroma, :is_moderator], user.is_moderator)
|
|
end
|
|
|
|
defp maybe_put_role(data, _, _), do: data
|
|
|
|
defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
|
|
Kernel.put_in(
|
|
data,
|
|
[:pleroma, :notification_settings],
|
|
Map.from_struct(user.notification_settings)
|
|
)
|
|
end
|
|
|
|
defp maybe_put_notification_settings(data, _, _), do: data
|
|
|
|
defp maybe_put_allow_following_move(data, %User{id: user_id} = user, %User{id: user_id}) do
|
|
Kernel.put_in(data, [:pleroma, :allow_following_move], user.allow_following_move)
|
|
end
|
|
|
|
defp maybe_put_allow_following_move(data, _, _), do: data
|
|
|
|
defp maybe_put_activation_status(data, user, %User{is_admin: true}) do
|
|
Kernel.put_in(data, [:pleroma, :deactivated], !user.is_active)
|
|
end
|
|
|
|
defp maybe_put_activation_status(data, _, _), do: data
|
|
|
|
defp maybe_put_unread_conversation_count(data, %User{id: user_id} = user, %User{id: user_id}) do
|
|
data
|
|
|> Kernel.put_in(
|
|
[:pleroma, :unread_conversation_count],
|
|
Pleroma.Conversation.Participation.unread_count(user)
|
|
)
|
|
end
|
|
|
|
defp maybe_put_unread_conversation_count(data, _, _), do: data
|
|
|
|
defp maybe_put_unread_notification_count(data, %User{id: user_id}, %User{id: user_id} = user) do
|
|
Kernel.put_in(
|
|
data,
|
|
[:pleroma, :unread_notifications_count],
|
|
Pleroma.Notification.unread_notifications_count(user)
|
|
)
|
|
end
|
|
|
|
defp maybe_put_unread_notification_count(data, _, _), do: data
|
|
|
|
defp maybe_put_email_address(data, %User{id: user_id}, %User{id: user_id} = user) do
|
|
Kernel.put_in(
|
|
data,
|
|
[:pleroma, :email],
|
|
user.email
|
|
)
|
|
end
|
|
|
|
defp maybe_put_email_address(data, _, _), do: data
|
|
|
|
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
|
|
defp image_url(_), do: nil
|
|
end
|