Mastodon API: Add a setting to hide follow/follower count from the user view (hide_follows_count
and hide_followers_count
)
This commit is contained in:
parent
a58f29b826
commit
450bf7a63c
9 changed files with 117 additions and 21 deletions
|
@ -94,6 +94,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: added `/auth/password` endpoint for password reset with rate limit.
|
- Mastodon API: added `/auth/password` endpoint for password reset with rate limit.
|
||||||
- Mastodon API: /api/v1/accounts/:id/statuses now supports nicknames or user id
|
- Mastodon API: /api/v1/accounts/:id/statuses now supports nicknames or user id
|
||||||
- Mastodon API: Improve support for the user profile custom fields
|
- Mastodon API: Improve support for the user profile custom fields
|
||||||
|
- Mastodon API: follower/following counters are nullified when `hide_follows`/`hide_followers` and `hide_follows_count`/`hide_followers_count` are set
|
||||||
- Admin API: Return users' tags when querying reports
|
- Admin API: Return users' tags when querying reports
|
||||||
- Admin API: Return avatar and display name when querying users
|
- Admin API: Return avatar and display name when querying users
|
||||||
- Admin API: Allow querying user by ID
|
- Admin API: Allow querying user by ID
|
||||||
|
|
|
@ -50,6 +50,8 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
|
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
|
||||||
- `hide_followers`: boolean, true when the user has follower hiding enabled
|
- `hide_followers`: boolean, true when the user has follower hiding enabled
|
||||||
- `hide_follows`: boolean, true when the user has follow hiding enabled
|
- `hide_follows`: boolean, true when the user has follow hiding enabled
|
||||||
|
- `hide_followers_count`: boolean, true when the user has follower stat hiding enabled
|
||||||
|
- `hide_follows_count`: boolean, true when the user has follow stat hiding enabled
|
||||||
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`
|
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`
|
||||||
- `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials`
|
- `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials`
|
||||||
- `deactivated`: boolean, true when the user is deactivated
|
- `deactivated`: boolean, true when the user is deactivated
|
||||||
|
@ -112,6 +114,8 @@ Additional parameters can be added to the JSON body/Form data:
|
||||||
- `no_rich_text` - if true, html tags are stripped from all statuses requested from the API
|
- `no_rich_text` - if true, html tags are stripped from all statuses requested from the API
|
||||||
- `hide_followers` - if true, user's followers will be hidden
|
- `hide_followers` - if true, user's followers will be hidden
|
||||||
- `hide_follows` - if true, user's follows will be hidden
|
- `hide_follows` - if true, user's follows will be hidden
|
||||||
|
- `hide_followers_count` - if true, user's follower count will be hidden
|
||||||
|
- `hide_follows_count` - if true, user's follow count will be hidden
|
||||||
- `hide_favorites` - if true, user's favorites timeline will be hidden
|
- `hide_favorites` - if true, user's favorites timeline will be hidden
|
||||||
- `show_role` - if true, user's role (e.g admin, moderator) will be exposed to anyone in the API
|
- `show_role` - if true, user's role (e.g admin, moderator) will be exposed to anyone in the API
|
||||||
- `default_scope` - the scope returned under `privacy` key in Source subentity
|
- `default_scope` - the scope returned under `privacy` key in Source subentity
|
||||||
|
|
|
@ -41,6 +41,8 @@ defmodule Pleroma.User.Info do
|
||||||
field(:topic, :string, default: nil)
|
field(:topic, :string, default: nil)
|
||||||
field(:hub, :string, default: nil)
|
field(:hub, :string, default: nil)
|
||||||
field(:salmon, :string, default: nil)
|
field(:salmon, :string, default: nil)
|
||||||
|
field(:hide_followers_count, :boolean, default: false)
|
||||||
|
field(:hide_follows_count, :boolean, default: false)
|
||||||
field(:hide_followers, :boolean, default: false)
|
field(:hide_followers, :boolean, default: false)
|
||||||
field(:hide_follows, :boolean, default: false)
|
field(:hide_follows, :boolean, default: false)
|
||||||
field(:hide_favorites, :boolean, default: true)
|
field(:hide_favorites, :boolean, default: true)
|
||||||
|
@ -262,6 +264,8 @@ def remote_user_creation(info, params) do
|
||||||
:salmon,
|
:salmon,
|
||||||
:hide_followers,
|
:hide_followers,
|
||||||
:hide_follows,
|
:hide_follows,
|
||||||
|
:hide_followers_count,
|
||||||
|
:hide_follows_count,
|
||||||
:follower_count,
|
:follower_count,
|
||||||
:fields,
|
:fields,
|
||||||
:following_count
|
:following_count
|
||||||
|
@ -281,7 +285,9 @@ def user_upgrade(info, params, remote? \\ false) do
|
||||||
:following_count,
|
:following_count,
|
||||||
:hide_follows,
|
:hide_follows,
|
||||||
:fields,
|
:fields,
|
||||||
:hide_followers
|
:hide_followers,
|
||||||
|
:hide_followers_count,
|
||||||
|
:hide_follows_count
|
||||||
])
|
])
|
||||||
|> validate_fields(remote?)
|
|> validate_fields(remote?)
|
||||||
end
|
end
|
||||||
|
@ -295,6 +301,8 @@ def profile_update(info, params) do
|
||||||
:banner,
|
:banner,
|
||||||
:hide_follows,
|
:hide_follows,
|
||||||
:hide_followers,
|
:hide_followers,
|
||||||
|
:hide_followers_count,
|
||||||
|
:hide_follows_count,
|
||||||
:hide_favorites,
|
:hide_favorites,
|
||||||
:background,
|
:background,
|
||||||
:show_role,
|
:show_role,
|
||||||
|
@ -458,7 +466,9 @@ def follow_information_update(info, params) do
|
||||||
:hide_followers,
|
:hide_followers,
|
||||||
:hide_follows,
|
:hide_follows,
|
||||||
:follower_count,
|
:follower_count,
|
||||||
:following_count
|
:following_count,
|
||||||
|
:hide_followers_count,
|
||||||
|
:hide_follows_count
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -118,30 +118,34 @@ def render("user.json", %{user: user}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("following.json", %{user: user, page: page} = opts) do
|
def render("following.json", %{user: user, page: page} = opts) do
|
||||||
showing = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
|
showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
|
||||||
|
showing_count = showing_items || !user.info.hide_follows_count
|
||||||
|
|
||||||
query = User.get_friends_query(user)
|
query = User.get_friends_query(user)
|
||||||
query = from(user in query, select: [:ap_id])
|
query = from(user in query, select: [:ap_id])
|
||||||
following = Repo.all(query)
|
following = Repo.all(query)
|
||||||
|
|
||||||
total =
|
total =
|
||||||
if showing do
|
if showing_count do
|
||||||
length(following)
|
length(following)
|
||||||
else
|
else
|
||||||
0
|
0
|
||||||
end
|
end
|
||||||
|
|
||||||
collection(following, "#{user.ap_id}/following", page, showing, total)
|
collection(following, "#{user.ap_id}/following", page, showing_items, total)
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("following.json", %{user: user} = opts) do
|
def render("following.json", %{user: user} = opts) do
|
||||||
showing = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
|
showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
|
||||||
|
showing_count = showing_items || !user.info.hide_follows_count
|
||||||
|
|
||||||
query = User.get_friends_query(user)
|
query = User.get_friends_query(user)
|
||||||
query = from(user in query, select: [:ap_id])
|
query = from(user in query, select: [:ap_id])
|
||||||
following = Repo.all(query)
|
following = Repo.all(query)
|
||||||
|
|
||||||
total =
|
total =
|
||||||
if showing do
|
if showing_count do
|
||||||
length(following)
|
length(following)
|
||||||
else
|
else
|
||||||
0
|
0
|
||||||
|
@ -152,7 +156,7 @@ def render("following.json", %{user: user} = opts) do
|
||||||
"type" => "OrderedCollection",
|
"type" => "OrderedCollection",
|
||||||
"totalItems" => total,
|
"totalItems" => total,
|
||||||
"first" =>
|
"first" =>
|
||||||
if showing do
|
if showing_items do
|
||||||
collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
|
collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
|
||||||
else
|
else
|
||||||
"#{user.ap_id}/following?page=1"
|
"#{user.ap_id}/following?page=1"
|
||||||
|
@ -162,32 +166,34 @@ def render("following.json", %{user: user} = opts) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("followers.json", %{user: user, page: page} = opts) do
|
def render("followers.json", %{user: user, page: page} = opts) do
|
||||||
showing = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
|
showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
|
||||||
|
showing_count = showing_items || !user.info.hide_followers_count
|
||||||
|
|
||||||
query = User.get_followers_query(user)
|
query = User.get_followers_query(user)
|
||||||
query = from(user in query, select: [:ap_id])
|
query = from(user in query, select: [:ap_id])
|
||||||
followers = Repo.all(query)
|
followers = Repo.all(query)
|
||||||
|
|
||||||
total =
|
total =
|
||||||
if showing do
|
if showing_count do
|
||||||
length(followers)
|
length(followers)
|
||||||
else
|
else
|
||||||
0
|
0
|
||||||
end
|
end
|
||||||
|
|
||||||
collection(followers, "#{user.ap_id}/followers", page, showing, total)
|
collection(followers, "#{user.ap_id}/followers", page, showing_items, total)
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("followers.json", %{user: user} = opts) do
|
def render("followers.json", %{user: user} = opts) do
|
||||||
showing = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
|
showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
|
||||||
|
showing_count = showing_items || !user.info.hide_followers_count
|
||||||
|
|
||||||
query = User.get_followers_query(user)
|
query = User.get_followers_query(user)
|
||||||
query = from(user in query, select: [:ap_id])
|
query = from(user in query, select: [:ap_id])
|
||||||
followers = Repo.all(query)
|
followers = Repo.all(query)
|
||||||
|
|
||||||
total =
|
total =
|
||||||
if showing do
|
if showing_count do
|
||||||
length(followers)
|
length(followers)
|
||||||
else
|
else
|
||||||
0
|
0
|
||||||
|
@ -198,8 +204,8 @@ def render("followers.json", %{user: user} = opts) do
|
||||||
"type" => "OrderedCollection",
|
"type" => "OrderedCollection",
|
||||||
"totalItems" => total,
|
"totalItems" => total,
|
||||||
"first" =>
|
"first" =>
|
||||||
if showing do
|
if showing_items do
|
||||||
collection(followers, "#{user.ap_id}/followers", 1, showing, total)
|
collection(followers, "#{user.ap_id}/followers", 1, showing_items, total)
|
||||||
else
|
else
|
||||||
"#{user.ap_id}/followers?page=1"
|
"#{user.ap_id}/followers?page=1"
|
||||||
end
|
end
|
||||||
|
|
|
@ -147,6 +147,8 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||||
[
|
[
|
||||||
:no_rich_text,
|
:no_rich_text,
|
||||||
:locked,
|
:locked,
|
||||||
|
:hide_followers_count,
|
||||||
|
:hide_follows_count,
|
||||||
:hide_followers,
|
:hide_followers,
|
||||||
:hide_follows,
|
:hide_follows,
|
||||||
:hide_favorites,
|
:hide_favorites,
|
||||||
|
|
|
@ -74,10 +74,18 @@ defp do_render("account.json", %{user: user} = opts) do
|
||||||
user_info = User.get_cached_user_info(user)
|
user_info = User.get_cached_user_info(user)
|
||||||
|
|
||||||
following_count =
|
following_count =
|
||||||
((!user.info.hide_follows or opts[:for] == user) && user_info.following_count) || 0
|
if !user.info.hide_follows_count or !user.info.hide_follows or opts[:for] == user do
|
||||||
|
user_info.following_count
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
|
||||||
followers_count =
|
followers_count =
|
||||||
((!user.info.hide_followers or opts[:for] == user) && user_info.follower_count) || 0
|
if !user.info.hide_followers_count or !user.info.hide_followers or opts[:for] == user do
|
||||||
|
user_info.follower_count
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
|
||||||
bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
|
bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
|
||||||
|
|
||||||
|
@ -138,6 +146,8 @@ defp do_render("account.json", %{user: user} = opts) do
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
confirmation_pending: user_info.confirmation_pending,
|
confirmation_pending: user_info.confirmation_pending,
|
||||||
tags: user.tags,
|
tags: user.tags,
|
||||||
|
hide_followers_count: user.info.hide_followers_count,
|
||||||
|
hide_follows_count: user.info.hide_follows_count,
|
||||||
hide_followers: user.info.hide_followers,
|
hide_followers: user.info.hide_followers,
|
||||||
hide_follows: user.info.hide_follows,
|
hide_follows: user.info.hide_follows,
|
||||||
hide_favorites: user.info.hide_favorites,
|
hide_favorites: user.info.hide_favorites,
|
||||||
|
|
|
@ -105,10 +105,20 @@ test "sets totalItems to zero when followers are hidden" do
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
{:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
|
{:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
|
||||||
assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})
|
assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})
|
||||||
info = Map.put(user.info, :hide_followers, true)
|
info = Map.merge(user.info, %{hide_followers_count: true, hide_followers: true})
|
||||||
user = Map.put(user, :info, info)
|
user = Map.put(user, :info, info)
|
||||||
assert %{"totalItems" => 0} = UserView.render("followers.json", %{user: user})
|
assert %{"totalItems" => 0} = UserView.render("followers.json", %{user: user})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "sets correct totalItems when followers are hidden but the follower counter is not" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
|
||||||
|
assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})
|
||||||
|
info = Map.merge(user.info, %{hide_followers_count: false, hide_followers: true})
|
||||||
|
user = Map.put(user, :info, info)
|
||||||
|
assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "following" do
|
describe "following" do
|
||||||
|
@ -117,9 +127,19 @@ test "sets totalItems to zero when follows are hidden" do
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
{:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
|
{:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
|
||||||
assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
|
assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
|
||||||
info = Map.put(user.info, :hide_follows, true)
|
info = Map.merge(user.info, %{hide_follows_count: true, hide_follows: true})
|
||||||
user = Map.put(user, :info, info)
|
user = Map.put(user, :info, info)
|
||||||
assert %{"totalItems" => 0} = UserView.render("following.json", %{user: user})
|
assert %{"totalItems" => 0} = UserView.render("following.json", %{user: user})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "sets correct totalItems when follows are hidden but the follow counter is not" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
|
||||||
|
assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
|
||||||
|
info = Map.merge(user.info, %{hide_follows_count: false, hide_follows: true})
|
||||||
|
user = Map.put(user, :info, info)
|
||||||
|
assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -128,6 +128,22 @@ test "updates the user's hide_followers status", %{conn: conn} do
|
||||||
assert user["pleroma"]["hide_followers"] == true
|
assert user["pleroma"]["hide_followers"] == true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "updates the user's hide_followers_count and hide_follows_count", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{
|
||||||
|
hide_followers_count: "true",
|
||||||
|
hide_follows_count: "true"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert user = json_response(conn, 200)
|
||||||
|
assert user["pleroma"]["hide_followers_count"] == true
|
||||||
|
assert user["pleroma"]["hide_follows_count"] == true
|
||||||
|
end
|
||||||
|
|
||||||
test "updates the user's skip_thread_containment option", %{conn: conn} do
|
test "updates the user's skip_thread_containment option", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,8 @@ test "Represent a user account" do
|
||||||
hide_favorites: true,
|
hide_favorites: true,
|
||||||
hide_followers: false,
|
hide_followers: false,
|
||||||
hide_follows: false,
|
hide_follows: false,
|
||||||
|
hide_followers_count: false,
|
||||||
|
hide_follows_count: false,
|
||||||
relationship: %{},
|
relationship: %{},
|
||||||
skip_thread_containment: false
|
skip_thread_containment: false
|
||||||
}
|
}
|
||||||
|
@ -147,6 +149,8 @@ test "Represent a Service(bot) account" do
|
||||||
hide_favorites: true,
|
hide_favorites: true,
|
||||||
hide_followers: false,
|
hide_followers: false,
|
||||||
hide_follows: false,
|
hide_follows: false,
|
||||||
|
hide_followers_count: false,
|
||||||
|
hide_follows_count: false,
|
||||||
relationship: %{},
|
relationship: %{},
|
||||||
skip_thread_containment: false
|
skip_thread_containment: false
|
||||||
}
|
}
|
||||||
|
@ -318,6 +322,8 @@ test "represent an embedded relationship" do
|
||||||
hide_favorites: true,
|
hide_favorites: true,
|
||||||
hide_followers: false,
|
hide_followers: false,
|
||||||
hide_follows: false,
|
hide_follows: false,
|
||||||
|
hide_followers_count: false,
|
||||||
|
hide_follows_count: false,
|
||||||
relationship: %{
|
relationship: %{
|
||||||
id: to_string(user.id),
|
id: to_string(user.id),
|
||||||
following: false,
|
following: false,
|
||||||
|
@ -361,8 +367,16 @@ test "sanitizes display names" do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "hiding follows/following" do
|
describe "hiding follows/following" do
|
||||||
test "shows when follows/following are hidden and sets follower/following count to 0" do
|
test "shows when follows/followers stats are hidden and sets follow/follower count to 0" do
|
||||||
user = insert(:user, info: %{hide_followers: true, hide_follows: true})
|
info = %{
|
||||||
|
hide_followers: true,
|
||||||
|
hide_followers_count: true,
|
||||||
|
hide_follows: true,
|
||||||
|
hide_follows_count: true
|
||||||
|
}
|
||||||
|
|
||||||
|
user = insert(:user, info: info)
|
||||||
|
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
{:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
|
{:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
|
||||||
{:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
|
{:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
|
||||||
|
@ -370,6 +384,19 @@ test "shows when follows/following are hidden and sets follower/following count
|
||||||
assert %{
|
assert %{
|
||||||
followers_count: 0,
|
followers_count: 0,
|
||||||
following_count: 0,
|
following_count: 0,
|
||||||
|
pleroma: %{hide_follows_count: true, hide_followers_count: true}
|
||||||
|
} = AccountView.render("account.json", %{user: user})
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows when follows/followers are hidden" do
|
||||||
|
user = insert(:user, info: %{hide_followers: true, hide_follows: true})
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
|
||||||
|
{:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
followers_count: 1,
|
||||||
|
following_count: 1,
|
||||||
pleroma: %{hide_follows: true, hide_followers: true}
|
pleroma: %{hide_follows: true, hide_followers: true}
|
||||||
} = AccountView.render("account.json", %{user: user})
|
} = AccountView.render("account.json", %{user: user})
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue