Implement muting, add it to the whole mastodon API See merge request pleroma/pleroma!319tags/v1.1.4
@@ -888,6 +888,30 @@ defmodule Pleroma.User do | |||
) | |||
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 | |||
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213) | |||
blocker = | |||
@@ -930,6 +954,8 @@ defmodule Pleroma.User do | |||
update_and_set_cache(cng) | |||
end | |||
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id) | |||
def blocks?(user, %{ap_id: ap_id}) do | |||
blocks = user.info.blocks | |||
domain_blocks = user.info.domain_blocks | |||
@@ -941,6 +967,9 @@ defmodule Pleroma.User do | |||
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), | |||
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(:blocks, {:array, :string}, default: []) | |||
field(:domain_blocks, {:array, :string}, default: []) | |||
field(:mutes, {:array, :string}, default: []) | |||
field(:deactivated, :boolean, default: false) | |||
field(:no_rich_text, :boolean, default: false) | |||
field(:ap_enabled, :boolean, default: false) | |||
@@ -74,6 +75,14 @@ defmodule Pleroma.User.Info do | |||
|> validate_required([:follower_count]) | |||
end | |||
def set_mutes(info, mutes) do | |||
params = %{mutes: mutes} | |||
info | |||
|> cast(params, [:mutes]) | |||
|> validate_required([:mutes]) | |||
end | |||
def set_blocks(info, blocks) do | |||
params = %{blocks: blocks} | |||
@@ -82,6 +91,14 @@ defmodule Pleroma.User.Info do | |||
|> validate_required([:blocks]) | |||
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 | |||
set_blocks(info, Enum.uniq([blocked | info.blocks])) | |||
end | |||
@@ -576,6 +576,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
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 | |||
blocks = info.blocks || [] | |||
domain_blocks = info.domain_blocks || [] | |||
@@ -629,6 +641,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
|> restrict_type(opts) | |||
|> restrict_favorited_by(opts) | |||
|> restrict_blocked(opts) | |||
|> restrict_muted(opts) | |||
|> restrict_media(opts) | |||
|> restrict_visibility(opts) | |||
|> restrict_replies(opts) | |||
@@ -232,6 +232,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
params | |||
|> Map.put("type", ["Create", "Announce"]) | |||
|> Map.put("blocking_user", user) | |||
|> Map.put("muting_user", user) | |||
|> Map.put("user", user) | |||
activities = | |||
@@ -254,6 +255,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
|> Map.put("type", ["Create", "Announce"]) | |||
|> Map.put("local_only", local_only) | |||
|> Map.put("blocking_user", user) | |||
|> Map.put("muting_user", user) | |||
|> ActivityPub.fetch_public_activities() | |||
|> Enum.reverse() | |||
@@ -620,6 +622,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
|> Map.put("type", "Create") | |||
|> Map.put("local_only", local_only) | |||
|> Map.put("blocking_user", user) | |||
|> Map.put("muting_user", user) | |||
|> Map.put("tag", tags) | |||
|> Map.put("tag_all", tag_all) | |||
|> Map.put("tag_reject", tag_reject) | |||
@@ -763,6 +766,41 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
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 | |||
with %User{} = blocked <- Repo.get(User, id), | |||
{:ok, blocker} <- User.block(blocker, blocked), | |||
@@ -1018,6 +1056,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
params | |||
|> Map.put("type", "Create") | |||
|> 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 | |||
# does not actually have permission to see (for more info, peruse security issue #270). | |||
@@ -47,7 +47,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do | |||
following: User.following?(user, target), | |||
followed_by: User.following?(target, user), | |||
blocking: User.blocks?(user, target), | |||
muting: false, | |||
muting: User.mutes?(user, target), | |||
muting_notifications: false, | |||
requested: requested, | |||
domain_blocking: false, | |||
@@ -168,8 +168,8 @@ defmodule Pleroma.Web.Router do | |||
post("/accounts/:id/unfollow", MastodonAPIController, :unfollow) | |||
post("/accounts/:id/block", MastodonAPIController, :block) | |||
post("/accounts/:id/unblock", MastodonAPIController, :unblock) | |||
post("/accounts/:id/mute", MastodonAPIController, :relationship_noop) | |||
post("/accounts/:id/unmute", MastodonAPIController, :relationship_noop) | |||
post("/accounts/:id/mute", MastodonAPIController, :mute) | |||
post("/accounts/:id/unmute", MastodonAPIController, :unmute) | |||
get("/accounts/:id/lists", MastodonAPIController, :account_lists) | |||
get("/follow_requests", MastodonAPIController, :follow_requests) | |||
@@ -180,7 +180,7 @@ defmodule Pleroma.Web.Router do | |||
get("/blocks", MastodonAPIController, :blocks) | |||
get("/mutes", MastodonAPIController, :empty_array) | |||
get("/mutes", MastodonAPIController, :mutes) | |||
get("/timelines/home", MastodonAPIController, :home_timeline) | |||
@@ -594,6 +594,29 @@ defmodule Pleroma.UserTest do | |||
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 | |||
test "it blocks people" do | |||
user = insert(:user) | |||
@@ -277,6 +277,48 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do | |||
assert Enum.member?(activities, activity_one) | |||
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 | |||
user = insert(:user) | |||
{:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user}) | |||
@@ -1206,6 +1206,42 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
assert id == to_string(other_user.id) | |||
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 | |||
user = insert(:user) | |||
other_user = insert(:user) | |||
@@ -1282,26 +1318,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
assert "even.worse.site" in domain_blocks | |||
end | |||
test "unimplemented mute endpoints" 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 | |||
test "unimplemented follow_requests, blocks, domain blocks" do | |||
user = insert(:user) | |||
["blocks", "domain_blocks", "mutes", "follow_requests"] | |||
["blocks", "domain_blocks", "follow_requests"] | |||
|> Enum.each(fn endpoint -> | |||
conn = | |||
build_conn() | |||