forked from AkkomaGang/akkoma
Merge branch 'muting' into 'develop'
Implement muting, add it to the whole mastodon API See merge request pleroma/pleroma!319
This commit is contained in:
commit
11b3c10c54
9 changed files with 205 additions and 22 deletions
|
@ -888,6 +888,30 @@ def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_i
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mute(muter, %User{ap_id: ap_id}) do
|
||||||
|
info_cng =
|
||||||
|
muter.info
|
||||||
|
|> User.Info.add_to_mutes(ap_id)
|
||||||
|
|
||||||
|
cng =
|
||||||
|
change(muter)
|
||||||
|
|> put_embed(:info, info_cng)
|
||||||
|
|
||||||
|
update_and_set_cache(cng)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unmute(muter, %{ap_id: ap_id}) do
|
||||||
|
info_cng =
|
||||||
|
muter.info
|
||||||
|
|> User.Info.remove_from_mutes(ap_id)
|
||||||
|
|
||||||
|
cng =
|
||||||
|
change(muter)
|
||||||
|
|> put_embed(:info, info_cng)
|
||||||
|
|
||||||
|
update_and_set_cache(cng)
|
||||||
|
end
|
||||||
|
|
||||||
def block(blocker, %User{ap_id: ap_id} = blocked) do
|
def block(blocker, %User{ap_id: ap_id} = blocked) do
|
||||||
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
|
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
|
||||||
blocker =
|
blocker =
|
||||||
|
@ -930,6 +954,8 @@ def unblock(blocker, %{ap_id: ap_id}) do
|
||||||
update_and_set_cache(cng)
|
update_and_set_cache(cng)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
|
||||||
|
|
||||||
def blocks?(user, %{ap_id: ap_id}) do
|
def blocks?(user, %{ap_id: ap_id}) do
|
||||||
blocks = user.info.blocks
|
blocks = user.info.blocks
|
||||||
domain_blocks = user.info.domain_blocks
|
domain_blocks = user.info.domain_blocks
|
||||||
|
@ -941,6 +967,9 @@ def blocks?(user, %{ap_id: ap_id}) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def muted_users(user),
|
||||||
|
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.mutes))
|
||||||
|
|
||||||
def blocked_users(user),
|
def blocked_users(user),
|
||||||
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks))
|
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks))
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ defmodule Pleroma.User.Info do
|
||||||
field(:default_scope, :string, default: "public")
|
field(:default_scope, :string, default: "public")
|
||||||
field(:blocks, {:array, :string}, default: [])
|
field(:blocks, {:array, :string}, default: [])
|
||||||
field(:domain_blocks, {:array, :string}, default: [])
|
field(:domain_blocks, {:array, :string}, default: [])
|
||||||
|
field(:mutes, {:array, :string}, default: [])
|
||||||
field(:deactivated, :boolean, default: false)
|
field(:deactivated, :boolean, default: false)
|
||||||
field(:no_rich_text, :boolean, default: false)
|
field(:no_rich_text, :boolean, default: false)
|
||||||
field(:ap_enabled, :boolean, default: false)
|
field(:ap_enabled, :boolean, default: false)
|
||||||
|
@ -74,6 +75,14 @@ def set_follower_count(info, number) do
|
||||||
|> validate_required([:follower_count])
|
|> validate_required([:follower_count])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_mutes(info, mutes) do
|
||||||
|
params = %{mutes: mutes}
|
||||||
|
|
||||||
|
info
|
||||||
|
|> cast(params, [:mutes])
|
||||||
|
|> validate_required([:mutes])
|
||||||
|
end
|
||||||
|
|
||||||
def set_blocks(info, blocks) do
|
def set_blocks(info, blocks) do
|
||||||
params = %{blocks: blocks}
|
params = %{blocks: blocks}
|
||||||
|
|
||||||
|
@ -82,6 +91,14 @@ def set_blocks(info, blocks) do
|
||||||
|> validate_required([:blocks])
|
|> validate_required([:blocks])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_to_mutes(info, muted) do
|
||||||
|
set_mutes(info, Enum.uniq([muted | info.mutes]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_from_mutes(info, muted) do
|
||||||
|
set_mutes(info, List.delete(info.mutes, muted))
|
||||||
|
end
|
||||||
|
|
||||||
def add_to_block(info, blocked) do
|
def add_to_block(info, blocked) do
|
||||||
set_blocks(info, Enum.uniq([blocked | info.blocks]))
|
set_blocks(info, Enum.uniq([blocked | info.blocks]))
|
||||||
end
|
end
|
||||||
|
|
|
@ -576,6 +576,18 @@ defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val == "true" or
|
||||||
|
|
||||||
defp restrict_reblogs(query, _), do: query
|
defp restrict_reblogs(query, _), do: query
|
||||||
|
|
||||||
|
defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do
|
||||||
|
mutes = info.mutes
|
||||||
|
|
||||||
|
from(
|
||||||
|
activity in query,
|
||||||
|
where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
|
||||||
|
where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_muted(query, _), do: query
|
||||||
|
|
||||||
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
||||||
blocks = info.blocks || []
|
blocks = info.blocks || []
|
||||||
domain_blocks = info.domain_blocks || []
|
domain_blocks = info.domain_blocks || []
|
||||||
|
@ -629,6 +641,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
|> restrict_type(opts)
|
|> restrict_type(opts)
|
||||||
|> restrict_favorited_by(opts)
|
|> restrict_favorited_by(opts)
|
||||||
|> restrict_blocked(opts)
|
|> restrict_blocked(opts)
|
||||||
|
|> restrict_muted(opts)
|
||||||
|> restrict_media(opts)
|
|> restrict_media(opts)
|
||||||
|> restrict_visibility(opts)
|
|> restrict_visibility(opts)
|
||||||
|> restrict_replies(opts)
|
|> restrict_replies(opts)
|
||||||
|
|
|
@ -232,6 +232,7 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
params
|
params
|
||||||
|> Map.put("type", ["Create", "Announce"])
|
|> Map.put("type", ["Create", "Announce"])
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("muting_user", user)
|
||||||
|> Map.put("user", user)
|
|> Map.put("user", user)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
|
@ -254,6 +255,7 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> Map.put("type", ["Create", "Announce"])
|
|> Map.put("type", ["Create", "Announce"])
|
||||||
|> Map.put("local_only", local_only)
|
|> Map.put("local_only", local_only)
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("muting_user", user)
|
||||||
|> ActivityPub.fetch_public_activities()
|
|> ActivityPub.fetch_public_activities()
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
@ -620,6 +622,7 @@ def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> Map.put("type", "Create")
|
|> Map.put("type", "Create")
|
||||||
|> Map.put("local_only", local_only)
|
|> Map.put("local_only", local_only)
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("muting_user", user)
|
||||||
|> Map.put("tag", tags)
|
|> Map.put("tag", tags)
|
||||||
|> Map.put("tag_all", tag_all)
|
|> Map.put("tag_all", tag_all)
|
||||||
|> Map.put("tag_reject", tag_reject)
|
|> Map.put("tag_reject", tag_reject)
|
||||||
|
@ -763,6 +766,41 @@ def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
||||||
|
with %User{} = muted <- Repo.get(User, id),
|
||||||
|
{:ok, muter} <- User.mute(muter, muted) do
|
||||||
|
conn
|
||||||
|
|> put_view(AccountView)
|
||||||
|
|> render("relationship.json", %{user: muter, target: muted})
|
||||||
|
else
|
||||||
|
{:error, message} ->
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/json")
|
||||||
|
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
||||||
|
with %User{} = muted <- Repo.get(User, id),
|
||||||
|
{:ok, muter} <- User.unmute(muter, muted) do
|
||||||
|
conn
|
||||||
|
|> put_view(AccountView)
|
||||||
|
|> render("relationship.json", %{user: muter, target: muted})
|
||||||
|
else
|
||||||
|
{:error, message} ->
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/json")
|
||||||
|
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def mutes(%{assigns: %{user: user}} = conn, _) do
|
||||||
|
with muted_accounts <- User.muted_users(user) do
|
||||||
|
res = AccountView.render("accounts.json", users: muted_accounts, for: user, as: :user)
|
||||||
|
json(conn, res)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
with %User{} = blocked <- Repo.get(User, id),
|
with %User{} = blocked <- Repo.get(User, id),
|
||||||
{:ok, blocker} <- User.block(blocker, blocked),
|
{:ok, blocker} <- User.block(blocker, blocked),
|
||||||
|
@ -1018,6 +1056,7 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params)
|
||||||
params
|
params
|
||||||
|> Map.put("type", "Create")
|
|> Map.put("type", "Create")
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("muting_user", user)
|
||||||
|
|
||||||
# we must filter the following list for the user to avoid leaking statuses the user
|
# we must filter the following list for the user to avoid leaking statuses the user
|
||||||
# does not actually have permission to see (for more info, peruse security issue #270).
|
# does not actually have permission to see (for more info, peruse security issue #270).
|
||||||
|
|
|
@ -47,7 +47,7 @@ def render("relationship.json", %{user: user, target: target}) do
|
||||||
following: User.following?(user, target),
|
following: User.following?(user, target),
|
||||||
followed_by: User.following?(target, user),
|
followed_by: User.following?(target, user),
|
||||||
blocking: User.blocks?(user, target),
|
blocking: User.blocks?(user, target),
|
||||||
muting: false,
|
muting: User.mutes?(user, target),
|
||||||
muting_notifications: false,
|
muting_notifications: false,
|
||||||
requested: requested,
|
requested: requested,
|
||||||
domain_blocking: false,
|
domain_blocking: false,
|
||||||
|
|
|
@ -168,8 +168,8 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/accounts/:id/unfollow", MastodonAPIController, :unfollow)
|
post("/accounts/:id/unfollow", MastodonAPIController, :unfollow)
|
||||||
post("/accounts/:id/block", MastodonAPIController, :block)
|
post("/accounts/:id/block", MastodonAPIController, :block)
|
||||||
post("/accounts/:id/unblock", MastodonAPIController, :unblock)
|
post("/accounts/:id/unblock", MastodonAPIController, :unblock)
|
||||||
post("/accounts/:id/mute", MastodonAPIController, :relationship_noop)
|
post("/accounts/:id/mute", MastodonAPIController, :mute)
|
||||||
post("/accounts/:id/unmute", MastodonAPIController, :relationship_noop)
|
post("/accounts/:id/unmute", MastodonAPIController, :unmute)
|
||||||
get("/accounts/:id/lists", MastodonAPIController, :account_lists)
|
get("/accounts/:id/lists", MastodonAPIController, :account_lists)
|
||||||
|
|
||||||
get("/follow_requests", MastodonAPIController, :follow_requests)
|
get("/follow_requests", MastodonAPIController, :follow_requests)
|
||||||
|
@ -180,7 +180,7 @@ defmodule Pleroma.Web.Router do
|
||||||
|
|
||||||
get("/blocks", MastodonAPIController, :blocks)
|
get("/blocks", MastodonAPIController, :blocks)
|
||||||
|
|
||||||
get("/mutes", MastodonAPIController, :empty_array)
|
get("/mutes", MastodonAPIController, :mutes)
|
||||||
|
|
||||||
get("/timelines/home", MastodonAPIController, :home_timeline)
|
get("/timelines/home", MastodonAPIController, :home_timeline)
|
||||||
|
|
||||||
|
|
|
@ -594,6 +594,29 @@ test "it imports user followings from list" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "mutes" do
|
||||||
|
test "it mutes people" do
|
||||||
|
user = insert(:user)
|
||||||
|
muted_user = insert(:user)
|
||||||
|
|
||||||
|
refute User.mutes?(user, muted_user)
|
||||||
|
|
||||||
|
{:ok, user} = User.mute(user, muted_user)
|
||||||
|
|
||||||
|
assert User.mutes?(user, muted_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it unmutes users" do
|
||||||
|
user = insert(:user)
|
||||||
|
muted_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user} = User.mute(user, muted_user)
|
||||||
|
{:ok, user} = User.unmute(user, muted_user)
|
||||||
|
|
||||||
|
refute User.mutes?(user, muted_user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "blocks" do
|
describe "blocks" do
|
||||||
test "it blocks people" do
|
test "it blocks people" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
|
@ -277,6 +277,48 @@ test "doesn't return blocked activities" do
|
||||||
assert Enum.member?(activities, activity_one)
|
assert Enum.member?(activities, activity_one)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "doesn't return muted activities" do
|
||||||
|
activity_one = insert(:note_activity)
|
||||||
|
activity_two = insert(:note_activity)
|
||||||
|
activity_three = insert(:note_activity)
|
||||||
|
user = insert(:user)
|
||||||
|
booster = insert(:user)
|
||||||
|
{:ok, user} = User.mute(user, %User{ap_id: activity_one.data["actor"]})
|
||||||
|
|
||||||
|
activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
|
||||||
|
|
||||||
|
assert Enum.member?(activities, activity_two)
|
||||||
|
assert Enum.member?(activities, activity_three)
|
||||||
|
refute Enum.member?(activities, activity_one)
|
||||||
|
|
||||||
|
{:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]})
|
||||||
|
|
||||||
|
activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
|
||||||
|
|
||||||
|
assert Enum.member?(activities, activity_two)
|
||||||
|
assert Enum.member?(activities, activity_three)
|
||||||
|
assert Enum.member?(activities, activity_one)
|
||||||
|
|
||||||
|
{:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]})
|
||||||
|
{:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
|
||||||
|
%Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
|
||||||
|
activity_three = Repo.get(Activity, activity_three.id)
|
||||||
|
|
||||||
|
activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
|
||||||
|
|
||||||
|
assert Enum.member?(activities, activity_two)
|
||||||
|
refute Enum.member?(activities, activity_three)
|
||||||
|
refute Enum.member?(activities, boost_activity)
|
||||||
|
assert Enum.member?(activities, activity_one)
|
||||||
|
|
||||||
|
activities = ActivityPub.fetch_activities([], %{"muting_user" => nil})
|
||||||
|
|
||||||
|
assert Enum.member?(activities, activity_two)
|
||||||
|
assert Enum.member?(activities, activity_three)
|
||||||
|
assert Enum.member?(activities, boost_activity)
|
||||||
|
assert Enum.member?(activities, activity_one)
|
||||||
|
end
|
||||||
|
|
||||||
test "excludes reblogs on request" do
|
test "excludes reblogs on request" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
|
{:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
|
||||||
|
|
|
@ -1206,6 +1206,42 @@ test "following / unfollowing a user", %{conn: conn} do
|
||||||
assert id == to_string(other_user.id)
|
assert id == to_string(other_user.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "muting / unmuting a user", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/accounts/#{other_user.id}/mute")
|
||||||
|
|
||||||
|
assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
|
||||||
|
|
||||||
|
user = Repo.get(User, user.id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/accounts/#{other_user.id}/unmute")
|
||||||
|
|
||||||
|
assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "getting a list of mutes", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user} = User.mute(user, other_user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/mutes")
|
||||||
|
|
||||||
|
other_user_id = to_string(other_user.id)
|
||||||
|
assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
|
||||||
|
end
|
||||||
|
|
||||||
test "blocking / unblocking a user", %{conn: conn} do
|
test "blocking / unblocking a user", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
@ -1282,26 +1318,10 @@ test "getting a list of domain blocks", %{conn: conn} do
|
||||||
assert "even.worse.site" in domain_blocks
|
assert "even.worse.site" in domain_blocks
|
||||||
end
|
end
|
||||||
|
|
||||||
test "unimplemented mute endpoints" do
|
test "unimplemented follow_requests, blocks, domain blocks" do
|
||||||
user = insert(:user)
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
["mute", "unmute"]
|
|
||||||
|> Enum.each(fn endpoint ->
|
|
||||||
conn =
|
|
||||||
build_conn()
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> post("/api/v1/accounts/#{other_user.id}/#{endpoint}")
|
|
||||||
|
|
||||||
assert %{"id" => id} = json_response(conn, 200)
|
|
||||||
assert id == to_string(other_user.id)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "unimplemented mutes, follow_requests, blocks, domain blocks" do
|
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
["blocks", "domain_blocks", "mutes", "follow_requests"]
|
["blocks", "domain_blocks", "follow_requests"]
|
||||||
|> Enum.each(fn endpoint ->
|
|> Enum.each(fn endpoint ->
|
||||||
conn =
|
conn =
|
||||||
build_conn()
|
build_conn()
|
||||||
|
|
Loading…
Reference in a new issue