Merge branch 'feature/1392-support-irreversible-filters' into 'develop'

Updates for Feature/1392 support irreversible filters

Closes #1392

See merge request pleroma/pleroma!2186
This commit is contained in:
Haelwenn 2020-07-07 08:23:49 +00:00
commit fa0fa4552f
10 changed files with 366 additions and 53 deletions

View file

@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Streaming: Repeats of a user's posts will no longer be pushed to the user's stream. - Streaming: Repeats of a user's posts will no longer be pushed to the user's stream.
- Mastodon API: Added `pleroma.metadata.fields_limits` to /api/v1/instance - Mastodon API: Added `pleroma.metadata.fields_limits` to /api/v1/instance
- Mastodon API: On deletion, returns the original post text. - Mastodon API: On deletion, returns the original post text.
- Mastodon API: Add `pleroma.unread_count` to the Marker entity.
</details> </details>
<details> <details>
@ -59,8 +60,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: Extended `/api/v1/instance`. - Mastodon API: Extended `/api/v1/instance`.
- Mastodon API: Support for `include_types` in `/api/v1/notifications`. - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint. - Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
- Mastodon API: Add support for filtering replies in public and home timelines - Mastodon API: Add support for filtering replies in public and home timelines.
- Mastodon API: Support for `bot` field in `/api/v1/accounts/update_credentials` - Mastodon API: Support for `bot` field in `/api/v1/accounts/update_credentials`.
- Mastodon API: Support irreversible property for filters.
- Admin API: endpoints for create/update/delete OAuth Apps. - Admin API: endpoints for create/update/delete OAuth Apps.
- Admin API: endpoint for status view. - Admin API: endpoint for status view.
- OTP: Add command to reload emoji packs - OTP: Add command to reload emoji packs
@ -215,7 +217,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: `pleroma.thread_muted` to the Status entity - Mastodon API: `pleroma.thread_muted` to the Status entity
- Mastodon API: Mark the direct conversation as read for the author when they send a new direct message - Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
- Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload. - Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload.
- Mastodon API: Add `pleroma.unread_count` to the Marker entity
- Admin API: Render whole status in grouped reports - Admin API: Render whole status in grouped reports
- Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise). - Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise).
- Mastodon API: Favoriting / Repeating a post multiple times will now return the identical response every time. Before, executing that action twice would return an error ("already favorited") on the second try. - Mastodon API: Favoriting / Repeating a post multiple times will now return the identical response every time. Before, executing that action twice would return an error ("already favorited") on the second try.

View file

@ -24,6 +24,7 @@ defmodule Pleroma.LoadTesting.Activities do
@visibility ~w(public private direct unlisted) @visibility ~w(public private direct unlisted)
@types [ @types [
:simple, :simple,
:simple_filtered,
:emoji, :emoji,
:mentions, :mentions,
:hell_thread, :hell_thread,
@ -242,6 +243,15 @@ defp insert_activity(:simple, visibility, group, users, _opts) do
insert_local_activity(visibility, group, users, "Simple status") insert_local_activity(visibility, group, users, "Simple status")
end end
defp insert_activity(:simple_filtered, visibility, group, users, _opts)
when group in @remote_groups do
insert_remote_activity(visibility, group, users, "Remote status which must be filtered")
end
defp insert_activity(:simple_filtered, visibility, group, users, _opts) do
insert_local_activity(visibility, group, users, "Simple status which must be filtered")
end
defp insert_activity(:emoji, visibility, group, users, _opts) defp insert_activity(:emoji, visibility, group, users, _opts)
when group in @remote_groups do when group in @remote_groups do
insert_remote_activity(visibility, group, users, "Remote status with emoji :firefox:") insert_remote_activity(visibility, group, users, "Remote status with emoji :firefox:")

View file

@ -32,10 +32,22 @@ defp fetch_user(user) do
) )
end end
defp create_filter(user) do
Pleroma.Filter.create(%Pleroma.Filter{
user_id: user.id,
phrase: "must be filtered",
hide: true
})
end
defp delete_filter(filter), do: Repo.delete(filter)
defp fetch_timelines(user) do defp fetch_timelines(user) do
fetch_home_timeline(user) fetch_home_timeline(user)
fetch_home_timeline_with_filter(user)
fetch_direct_timeline(user) fetch_direct_timeline(user)
fetch_public_timeline(user) fetch_public_timeline(user)
fetch_public_timeline_with_filter(user)
fetch_public_timeline(user, :with_blocks) fetch_public_timeline(user, :with_blocks)
fetch_public_timeline(user, :local) fetch_public_timeline(user, :local)
fetch_public_timeline(user, :tag) fetch_public_timeline(user, :tag)
@ -61,7 +73,7 @@ defp opts_for_home_timeline(user) do
} }
end end
defp fetch_home_timeline(user) do defp fetch_home_timeline(user, title_end \\ "") do
opts = opts_for_home_timeline(user) opts = opts_for_home_timeline(user)
recipients = [user.ap_id | User.following(user)] recipients = [user.ap_id | User.following(user)]
@ -84,9 +96,11 @@ defp fetch_home_timeline(user) do
|> Enum.reverse() |> Enum.reverse()
|> List.last() |> List.last()
title = "home timeline " <> title_end
Benchee.run( Benchee.run(
%{ %{
"home timeline" => fn opts -> ActivityPub.fetch_activities(recipients, opts) end title => fn opts -> ActivityPub.fetch_activities(recipients, opts) end
}, },
inputs: %{ inputs: %{
"1 page" => opts, "1 page" => opts,
@ -108,6 +122,14 @@ defp fetch_home_timeline(user) do
) )
end end
defp fetch_home_timeline_with_filter(user) do
{:ok, filter} = create_filter(user)
fetch_home_timeline(user, "with filters")
delete_filter(filter)
end
defp opts_for_direct_timeline(user) do defp opts_for_direct_timeline(user) do
%{ %{
visibility: "direct", visibility: "direct",
@ -210,6 +232,14 @@ defp fetch_public_timeline(user) do
fetch_public_timeline(opts, "public timeline") fetch_public_timeline(opts, "public timeline")
end end
defp fetch_public_timeline_with_filter(user) do
{:ok, filter} = create_filter(user)
opts = opts_for_public_timeline(user)
fetch_public_timeline(opts, "public timeline with filters")
delete_filter(filter)
end
defp fetch_public_timeline(user, :local) do defp fetch_public_timeline(user, :local) do
opts = opts_for_public_timeline(user, :local) opts = opts_for_public_timeline(user, :local)

View file

@ -34,10 +34,18 @@ def get(id, %{id: user_id} = _user) do
Repo.one(query) Repo.one(query)
end end
def get_filters(%User{id: user_id} = _user) do def get_active(query) do
from(f in query, where: is_nil(f.expires_at) or f.expires_at > ^NaiveDateTime.utc_now())
end
def get_irreversible(query) do
from(f in query, where: f.hide)
end
def get_filters(query \\ __MODULE__, %User{id: user_id}) do
query = query =
from( from(
f in Pleroma.Filter, f in query,
where: f.user_id == ^user_id, where: f.user_id == ^user_id,
order_by: [desc: :id] order_by: [desc: :id]
) )
@ -95,4 +103,34 @@ def update(%Pleroma.Filter{} = filter, params) do
|> validate_required([:phrase, :context]) |> validate_required([:phrase, :context])
|> Repo.update() |> Repo.update()
end end
def compose_regex(user_or_filters, format \\ :postgres)
def compose_regex(%User{} = user, format) do
__MODULE__
|> get_active()
|> get_irreversible()
|> get_filters(user)
|> compose_regex(format)
end
def compose_regex([_ | _] = filters, format) do
phrases =
filters
|> Enum.map(& &1.phrase)
|> Enum.join("|")
case format do
:postgres ->
"\\y(#{phrases})\\y"
:re ->
~r/\b#{phrases}\b/i
_ ->
nil
end
end
def compose_regex(_, _), do: nil
end end

View file

@ -130,6 +130,7 @@ def for_user_query(user, opts \\ %{}) do
|> preload([n, a, o], activity: {a, object: o}) |> preload([n, a, o], activity: {a, object: o})
|> exclude_notification_muted(user, exclude_notification_muted_opts) |> exclude_notification_muted(user, exclude_notification_muted_opts)
|> exclude_blocked(user, exclude_blocked_opts) |> exclude_blocked(user, exclude_blocked_opts)
|> exclude_filtered(user)
|> exclude_visibility(opts) |> exclude_visibility(opts)
end end
@ -158,6 +159,20 @@ defp exclude_notification_muted(query, user, opts) do
|> where([n, a, o, tm], is_nil(tm.user_id)) |> where([n, a, o, tm], is_nil(tm.user_id))
end end
defp exclude_filtered(query, user) do
case Pleroma.Filter.compose_regex(user) do
nil ->
query
regex ->
from([_n, a, o] in query,
where:
fragment("not(?->>'content' ~* ?)", o.data, ^regex) or
fragment("?->>'actor' = ?", o.data, ^user.ap_id)
)
end
end
@valid_visibilities ~w[direct unlisted public private] @valid_visibilities ~w[direct unlisted public private]
defp exclude_visibility(query, %{exclude_visibilities: visibility}) defp exclude_visibility(query, %{exclude_visibilities: visibility})
@ -337,6 +352,7 @@ def dismiss(%{id: user_id} = _user, id) do
end end
end end
@spec create_notifications(Activity.t(), keyword()) :: {:ok, [Notification.t()] | []}
def create_notifications(activity, options \\ []) def create_notifications(activity, options \\ [])
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity, options) do def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity, options) do
@ -555,7 +571,8 @@ def skip?(%Activity{} = activity, %User{} = user) do
:follows, :follows,
:non_followers, :non_followers,
:non_follows, :non_follows,
:recently_followed :recently_followed,
:filtered
] ]
|> Enum.find(&skip?(&1, activity, user)) |> Enum.find(&skip?(&1, activity, user))
end end
@ -624,6 +641,26 @@ def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity,
end) end)
end end
def skip?(:filtered, %{data: %{"type" => type}}, _) when type in ["Follow", "Move"], do: false
def skip?(:filtered, activity, user) do
object = Object.normalize(activity)
cond do
is_nil(object) ->
false
object.data["actor"] == user.ap_id ->
false
not is_nil(regex = Pleroma.Filter.compose_regex(user, :re)) ->
Regex.match?(regex, object.data["content"])
true ->
false
end
end
def skip?(_, _, _), do: false def skip?(_, _, _), do: false
def for_user_and_activity(user, activity) do def for_user_and_activity(user, activity) do

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Constants alias Pleroma.Constants
alias Pleroma.Conversation alias Pleroma.Conversation
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
alias Pleroma.Filter
alias Pleroma.Maps alias Pleroma.Maps
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
@ -446,6 +447,7 @@ def fetch_activities_for_context_query(context, opts) do
|> maybe_set_thread_muted_field(opts) |> maybe_set_thread_muted_field(opts)
|> restrict_blocked(opts) |> restrict_blocked(opts)
|> restrict_recipients(recipients, opts[:user]) |> restrict_recipients(recipients, opts[:user])
|> restrict_filtered(opts)
|> where( |> where(
[activity], [activity],
fragment( fragment(
@ -961,6 +963,26 @@ defp restrict_instance(query, %{instance: instance}) do
defp restrict_instance(query, _), do: query defp restrict_instance(query, _), do: query
defp restrict_filtered(query, %{user: %User{} = user}) do
case Filter.compose_regex(user) do
nil ->
query
regex ->
from([activity, object] in query,
where:
fragment("not(?->>'content' ~* ?)", object.data, ^regex) or
activity.actor == ^user.ap_id
)
end
end
defp restrict_filtered(query, %{blocking_user: %User{} = user}) do
restrict_filtered(query, %{user: user})
end
defp restrict_filtered(query, _), do: query
defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query
defp exclude_poll_votes(query, _) do defp exclude_poll_votes(query, _) do
@ -1091,6 +1113,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|> restrict_favorited_by(opts) |> restrict_favorited_by(opts)
|> restrict_blocked(restrict_blocked_opts) |> restrict_blocked(restrict_blocked_opts)
|> restrict_muted(restrict_muted_opts) |> restrict_muted(restrict_muted_opts)
|> restrict_filtered(opts)
|> restrict_media(opts) |> restrict_media(opts)
|> restrict_visibility(opts) |> restrict_visibility(opts)
|> restrict_thread_visibility(opts, config) |> restrict_thread_visibility(opts, config)
@ -1099,6 +1122,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|> restrict_muted_reblogs(restrict_muted_reblogs_opts) |> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|> restrict_instance(opts) |> restrict_instance(opts)
|> restrict_announce_object_actor(opts) |> restrict_announce_object_actor(opts)
|> restrict_filtered(opts)
|> Activity.restrict_deactivated_users() |> Activity.restrict_deactivated_users()
|> exclude_poll_votes(opts) |> exclude_poll_votes(opts)
|> exclude_chat_messages(opts) |> exclude_chat_messages(opts)

View file

@ -3,37 +3,39 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.FilterTest do defmodule Pleroma.FilterTest do
alias Pleroma.Repo
use Pleroma.DataCase use Pleroma.DataCase
import Pleroma.Factory import Pleroma.Factory
alias Pleroma.Filter
alias Pleroma.Repo
describe "creating filters" do describe "creating filters" do
test "creating one filter" do test "creating one filter" do
user = insert(:user) user = insert(:user)
query = %Pleroma.Filter{ query = %Filter{
user_id: user.id, user_id: user.id,
filter_id: 42, filter_id: 42,
phrase: "knights", phrase: "knights",
context: ["home"] context: ["home"]
} }
{:ok, %Pleroma.Filter{} = filter} = Pleroma.Filter.create(query) {:ok, %Filter{} = filter} = Filter.create(query)
result = Pleroma.Filter.get(filter.filter_id, user) result = Filter.get(filter.filter_id, user)
assert query.phrase == result.phrase assert query.phrase == result.phrase
end end
test "creating one filter without a pre-defined filter_id" do test "creating one filter without a pre-defined filter_id" do
user = insert(:user) user = insert(:user)
query = %Pleroma.Filter{ query = %Filter{
user_id: user.id, user_id: user.id,
phrase: "knights", phrase: "knights",
context: ["home"] context: ["home"]
} }
{:ok, %Pleroma.Filter{} = filter} = Pleroma.Filter.create(query) {:ok, %Filter{} = filter} = Filter.create(query)
# Should start at 1 # Should start at 1
assert filter.filter_id == 1 assert filter.filter_id == 1
end end
@ -41,23 +43,23 @@ test "creating one filter without a pre-defined filter_id" do
test "creating additional filters uses previous highest filter_id + 1" do test "creating additional filters uses previous highest filter_id + 1" do
user = insert(:user) user = insert(:user)
query_one = %Pleroma.Filter{ query_one = %Filter{
user_id: user.id, user_id: user.id,
filter_id: 42, filter_id: 42,
phrase: "knights", phrase: "knights",
context: ["home"] context: ["home"]
} }
{:ok, %Pleroma.Filter{} = filter_one} = Pleroma.Filter.create(query_one) {:ok, %Filter{} = filter_one} = Filter.create(query_one)
query_two = %Pleroma.Filter{ query_two = %Filter{
user_id: user.id, user_id: user.id,
# No filter_id # No filter_id
phrase: "who", phrase: "who",
context: ["home"] context: ["home"]
} }
{:ok, %Pleroma.Filter{} = filter_two} = Pleroma.Filter.create(query_two) {:ok, %Filter{} = filter_two} = Filter.create(query_two)
assert filter_two.filter_id == filter_one.filter_id + 1 assert filter_two.filter_id == filter_one.filter_id + 1
end end
@ -65,29 +67,29 @@ test "filter_id is unique per user" do
user_one = insert(:user) user_one = insert(:user)
user_two = insert(:user) user_two = insert(:user)
query_one = %Pleroma.Filter{ query_one = %Filter{
user_id: user_one.id, user_id: user_one.id,
phrase: "knights", phrase: "knights",
context: ["home"] context: ["home"]
} }
{:ok, %Pleroma.Filter{} = filter_one} = Pleroma.Filter.create(query_one) {:ok, %Filter{} = filter_one} = Filter.create(query_one)
query_two = %Pleroma.Filter{ query_two = %Filter{
user_id: user_two.id, user_id: user_two.id,
phrase: "who", phrase: "who",
context: ["home"] context: ["home"]
} }
{:ok, %Pleroma.Filter{} = filter_two} = Pleroma.Filter.create(query_two) {:ok, %Filter{} = filter_two} = Filter.create(query_two)
assert filter_one.filter_id == 1 assert filter_one.filter_id == 1
assert filter_two.filter_id == 1 assert filter_two.filter_id == 1
result_one = Pleroma.Filter.get(filter_one.filter_id, user_one) result_one = Filter.get(filter_one.filter_id, user_one)
assert result_one.phrase == filter_one.phrase assert result_one.phrase == filter_one.phrase
result_two = Pleroma.Filter.get(filter_two.filter_id, user_two) result_two = Filter.get(filter_two.filter_id, user_two)
assert result_two.phrase == filter_two.phrase assert result_two.phrase == filter_two.phrase
end end
end end
@ -95,38 +97,38 @@ test "filter_id is unique per user" do
test "deleting a filter" do test "deleting a filter" do
user = insert(:user) user = insert(:user)
query = %Pleroma.Filter{ query = %Filter{
user_id: user.id, user_id: user.id,
filter_id: 0, filter_id: 0,
phrase: "knights", phrase: "knights",
context: ["home"] context: ["home"]
} }
{:ok, _filter} = Pleroma.Filter.create(query) {:ok, _filter} = Filter.create(query)
{:ok, filter} = Pleroma.Filter.delete(query) {:ok, filter} = Filter.delete(query)
assert is_nil(Repo.get(Pleroma.Filter, filter.filter_id)) assert is_nil(Repo.get(Filter, filter.filter_id))
end end
test "getting all filters by an user" do test "getting all filters by an user" do
user = insert(:user) user = insert(:user)
query_one = %Pleroma.Filter{ query_one = %Filter{
user_id: user.id, user_id: user.id,
filter_id: 1, filter_id: 1,
phrase: "knights", phrase: "knights",
context: ["home"] context: ["home"]
} }
query_two = %Pleroma.Filter{ query_two = %Filter{
user_id: user.id, user_id: user.id,
filter_id: 2, filter_id: 2,
phrase: "who", phrase: "who",
context: ["home"] context: ["home"]
} }
{:ok, filter_one} = Pleroma.Filter.create(query_one) {:ok, filter_one} = Filter.create(query_one)
{:ok, filter_two} = Pleroma.Filter.create(query_two) {:ok, filter_two} = Filter.create(query_two)
filters = Pleroma.Filter.get_filters(user) filters = Filter.get_filters(user)
assert filter_one in filters assert filter_one in filters
assert filter_two in filters assert filter_two in filters
end end
@ -134,7 +136,7 @@ test "getting all filters by an user" do
test "updating a filter" do test "updating a filter" do
user = insert(:user) user = insert(:user)
query_one = %Pleroma.Filter{ query_one = %Filter{
user_id: user.id, user_id: user.id,
filter_id: 1, filter_id: 1,
phrase: "knights", phrase: "knights",
@ -146,8 +148,9 @@ test "updating a filter" do
context: ["home", "timeline"] context: ["home", "timeline"]
} }
{:ok, filter_one} = Pleroma.Filter.create(query_one) {:ok, filter_one} = Filter.create(query_one)
{:ok, filter_two} = Pleroma.Filter.update(filter_one, changes) {:ok, filter_two} = Filter.update(filter_one, changes)
assert filter_one != filter_two assert filter_one != filter_two
assert filter_two.phrase == changes.phrase assert filter_two.phrase == changes.phrase
assert filter_two.context == changes.context assert filter_two.context == changes.context

View file

@ -324,6 +324,44 @@ test "it disables notifications from people who are invisible" do
{:ok, status} = CommonAPI.post(author, %{status: "hey @#{user.nickname}"}) {:ok, status} = CommonAPI.post(author, %{status: "hey @#{user.nickname}"})
refute Notification.create_notification(status, user) refute Notification.create_notification(status, user)
end end
test "it doesn't create notifications if content matches with an irreversible filter" do
user = insert(:user)
subscriber = insert(:user)
User.subscribe(subscriber, user)
insert(:filter, user: subscriber, phrase: "cofe", hide: true)
{:ok, status} = CommonAPI.post(user, %{status: "got cofe?"})
assert {:ok, []} == Notification.create_notifications(status)
end
test "it creates notifications if content matches with a not irreversible filter" do
user = insert(:user)
subscriber = insert(:user)
User.subscribe(subscriber, user)
insert(:filter, user: subscriber, phrase: "cofe", hide: false)
{:ok, status} = CommonAPI.post(user, %{status: "got cofe?"})
{:ok, [notification]} = Notification.create_notifications(status)
assert notification
end
test "it creates notifications when someone likes user's status with a filtered word" do
user = insert(:user)
other_user = insert(:user)
insert(:filter, user: user, phrase: "tesla", hide: true)
{:ok, activity_one} = CommonAPI.post(user, %{status: "wow tesla"})
{:ok, activity_two} = CommonAPI.favorite(other_user, activity_one.id)
{:ok, [notification]} = Notification.create_notifications(activity_two)
assert notification
end
end end
describe "follow / follow_request notifications" do describe "follow / follow_request notifications" do
@ -990,8 +1028,13 @@ test "move activity generates a notification" do
end end
describe "for_user" do describe "for_user" do
test "it returns notifications for muted user without notifications" do setup do
user = insert(:user) user = insert(:user)
{:ok, %{user: user}}
end
test "it returns notifications for muted user without notifications", %{user: user} do
muted = insert(:user) muted = insert(:user)
{:ok, _user_relationships} = User.mute(user, muted, false) {:ok, _user_relationships} = User.mute(user, muted, false)
@ -1002,8 +1045,7 @@ test "it returns notifications for muted user without notifications" do
assert notification.activity.object assert notification.activity.object
end end
test "it doesn't return notifications for muted user with notifications" do test "it doesn't return notifications for muted user with notifications", %{user: user} do
user = insert(:user)
muted = insert(:user) muted = insert(:user)
{:ok, _user_relationships} = User.mute(user, muted) {:ok, _user_relationships} = User.mute(user, muted)
@ -1012,8 +1054,7 @@ test "it doesn't return notifications for muted user with notifications" do
assert Notification.for_user(user) == [] assert Notification.for_user(user) == []
end end
test "it doesn't return notifications for blocked user" do test "it doesn't return notifications for blocked user", %{user: user} do
user = insert(:user)
blocked = insert(:user) blocked = insert(:user)
{:ok, _user_relationship} = User.block(user, blocked) {:ok, _user_relationship} = User.block(user, blocked)
@ -1022,8 +1063,7 @@ test "it doesn't return notifications for blocked user" do
assert Notification.for_user(user) == [] assert Notification.for_user(user) == []
end end
test "it doesn't return notifications for domain-blocked non-followed user" do test "it doesn't return notifications for domain-blocked non-followed user", %{user: user} do
user = insert(:user)
blocked = insert(:user, ap_id: "http://some-domain.com") blocked = insert(:user, ap_id: "http://some-domain.com")
{:ok, user} = User.block_domain(user, "some-domain.com") {:ok, user} = User.block_domain(user, "some-domain.com")
@ -1044,8 +1084,7 @@ test "it returns notifications for domain-blocked but followed user" do
assert length(Notification.for_user(user)) == 1 assert length(Notification.for_user(user)) == 1
end end
test "it doesn't return notifications for muted thread" do test "it doesn't return notifications for muted thread", %{user: user} do
user = insert(:user)
another_user = insert(:user) another_user = insert(:user)
{:ok, activity} = CommonAPI.post(another_user, %{status: "hey @#{user.nickname}"}) {:ok, activity} = CommonAPI.post(another_user, %{status: "hey @#{user.nickname}"})
@ -1054,8 +1093,7 @@ test "it doesn't return notifications for muted thread" do
assert Notification.for_user(user) == [] assert Notification.for_user(user) == []
end end
test "it returns notifications from a muted user when with_muted is set" do test "it returns notifications from a muted user when with_muted is set", %{user: user} do
user = insert(:user)
muted = insert(:user) muted = insert(:user)
{:ok, _user_relationships} = User.mute(user, muted) {:ok, _user_relationships} = User.mute(user, muted)
@ -1064,8 +1102,9 @@ test "it returns notifications from a muted user when with_muted is set" do
assert length(Notification.for_user(user, %{with_muted: true})) == 1 assert length(Notification.for_user(user, %{with_muted: true})) == 1
end end
test "it doesn't return notifications from a blocked user when with_muted is set" do test "it doesn't return notifications from a blocked user when with_muted is set", %{
user = insert(:user) user: user
} do
blocked = insert(:user) blocked = insert(:user)
{:ok, _user_relationship} = User.block(user, blocked) {:ok, _user_relationship} = User.block(user, blocked)
@ -1075,8 +1114,8 @@ test "it doesn't return notifications from a blocked user when with_muted is set
end end
test "when with_muted is set, " <> test "when with_muted is set, " <>
"it doesn't return notifications from a domain-blocked non-followed user" do "it doesn't return notifications from a domain-blocked non-followed user",
user = insert(:user) %{user: user} do
blocked = insert(:user, ap_id: "http://some-domain.com") blocked = insert(:user, ap_id: "http://some-domain.com")
{:ok, user} = User.block_domain(user, "some-domain.com") {:ok, user} = User.block_domain(user, "some-domain.com")
@ -1085,8 +1124,7 @@ test "when with_muted is set, " <>
assert Enum.empty?(Notification.for_user(user, %{with_muted: true})) assert Enum.empty?(Notification.for_user(user, %{with_muted: true}))
end end
test "it returns notifications from muted threads when with_muted is set" do test "it returns notifications from muted threads when with_muted is set", %{user: user} do
user = insert(:user)
another_user = insert(:user) another_user = insert(:user)
{:ok, activity} = CommonAPI.post(another_user, %{status: "hey @#{user.nickname}"}) {:ok, activity} = CommonAPI.post(another_user, %{status: "hey @#{user.nickname}"})
@ -1094,5 +1132,33 @@ test "it returns notifications from muted threads when with_muted is set" do
{:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"]) {:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"])
assert length(Notification.for_user(user, %{with_muted: true})) == 1 assert length(Notification.for_user(user, %{with_muted: true})) == 1
end end
test "it doesn't return notifications about mentions with filtered word", %{user: user} do
insert(:filter, user: user, phrase: "cofe", hide: true)
another_user = insert(:user)
{:ok, _activity} = CommonAPI.post(another_user, %{status: "@#{user.nickname} got cofe?"})
assert Enum.empty?(Notification.for_user(user))
end
test "it returns notifications about mentions with not hidden filtered word", %{user: user} do
insert(:filter, user: user, phrase: "test", hide: false)
another_user = insert(:user)
{:ok, _} = CommonAPI.post(another_user, %{status: "@#{user.nickname} test"})
assert length(Notification.for_user(user)) == 1
end
test "it returns notifications about favorites with filtered word", %{user: user} do
insert(:filter, user: user, phrase: "cofe", hide: true)
another_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "Give me my cofe!"})
{:ok, _} = CommonAPI.favorite(another_user, activity.id)
assert length(Notification.for_user(user)) == 1
end
end end
end end

View file

@ -428,4 +428,12 @@ def mfa_token_factory do
user: build(:user) user: build(:user)
} }
end end
def filter_factory do
%Pleroma.Filter{
user: build(:user),
filter_id: sequence(:filter_id, & &1),
phrase: "cofe"
}
end
end end

View file

@ -507,6 +507,33 @@ test "retrieves activities that have a given context" do
activities = ActivityPub.fetch_activities_for_context("2hu", %{blocking_user: user}) activities = ActivityPub.fetch_activities_for_context("2hu", %{blocking_user: user})
assert activities == [activity_two, activity] assert activities == [activity_two, activity]
end end
test "doesn't return activities with filtered words" do
user = insert(:user)
user_two = insert(:user)
insert(:filter, user: user, phrase: "test", hide: true)
{:ok, %{id: id1, data: %{"context" => context}}} = CommonAPI.post(user, %{status: "1"})
{:ok, %{id: id2}} = CommonAPI.post(user_two, %{status: "2", in_reply_to_status_id: id1})
{:ok, %{id: id3} = user_activity} =
CommonAPI.post(user, %{status: "3 test?", in_reply_to_status_id: id2})
{:ok, %{id: id4} = filtered_activity} =
CommonAPI.post(user_two, %{status: "4 test!", in_reply_to_status_id: id3})
{:ok, _} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
activities =
context
|> ActivityPub.fetch_activities_for_context(%{user: user})
|> Enum.map(& &1.id)
assert length(activities) == 4
assert user_activity.id in activities
refute filtered_activity.id in activities
end
end end
test "doesn't return blocked activities" do test "doesn't return blocked activities" do
@ -785,6 +812,75 @@ test "excludes reblogs on request" do
assert activity == expected_activity assert activity == expected_activity
end end
describe "irreversible filters" do
setup do
user = insert(:user)
user_two = insert(:user)
insert(:filter, user: user_two, phrase: "cofe", hide: true)
insert(:filter, user: user_two, phrase: "ok boomer", hide: true)
insert(:filter, user: user_two, phrase: "test", hide: false)
params = %{
type: ["Create", "Announce"],
user: user_two
}
{:ok, %{user: user, user_two: user_two, params: params}}
end
test "it returns statuses if they don't contain exact filter words", %{
user: user,
params: params
} do
{:ok, _} = CommonAPI.post(user, %{status: "hey"})
{:ok, _} = CommonAPI.post(user, %{status: "got cofefe?"})
{:ok, _} = CommonAPI.post(user, %{status: "I am not a boomer"})
{:ok, _} = CommonAPI.post(user, %{status: "ok boomers"})
{:ok, _} = CommonAPI.post(user, %{status: "ccofee is not a word"})
{:ok, _} = CommonAPI.post(user, %{status: "this is a test"})
activities = ActivityPub.fetch_activities([], params)
assert Enum.count(activities) == 6
end
test "it does not filter user's own statuses", %{user_two: user_two, params: params} do
{:ok, _} = CommonAPI.post(user_two, %{status: "Give me some cofe!"})
{:ok, _} = CommonAPI.post(user_two, %{status: "ok boomer"})
activities = ActivityPub.fetch_activities([], params)
assert Enum.count(activities) == 2
end
test "it excludes statuses with filter words", %{user: user, params: params} do
{:ok, _} = CommonAPI.post(user, %{status: "Give me some cofe!"})
{:ok, _} = CommonAPI.post(user, %{status: "ok boomer"})
{:ok, _} = CommonAPI.post(user, %{status: "is it a cOfE?"})
{:ok, _} = CommonAPI.post(user, %{status: "cofe is all I need"})
{:ok, _} = CommonAPI.post(user, %{status: "— ok BOOMER\n"})
activities = ActivityPub.fetch_activities([], params)
assert Enum.empty?(activities)
end
test "it returns all statuses if user does not have any filters" do
another_user = insert(:user)
{:ok, _} = CommonAPI.post(another_user, %{status: "got cofe?"})
{:ok, _} = CommonAPI.post(another_user, %{status: "test!"})
activities =
ActivityPub.fetch_activities([], %{
type: ["Create", "Announce"],
user: another_user
})
assert Enum.count(activities) == 2
end
end
describe "public fetch activities" do describe "public fetch activities" do
test "doesn't retrieve unlisted activities" do test "doesn't retrieve unlisted activities" do
user = insert(:user) user = insert(:user)