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:
commit
fa0fa4552f
|
@ -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.
|
||||||
|
|
|
@ -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:")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue