Skip to content

Commit

Permalink
Irreversible filter works with user streaming. (#261)
Browse files Browse the repository at this point in the history
  • Loading branch information
kphrx authored Aug 6, 2024
2 parents a9a5464 + f1337bb commit a4b5800
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 86 deletions.
1 change: 1 addition & 0 deletions changelog.d/3542.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Irreversible filter works with user streaming.
24 changes: 18 additions & 6 deletions lib/pleroma/filter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ defmodule Pleroma.Filter do
from(f in query, where: f.hide)
end

@spec get_context(Ecto.Query.t(), String.t()) :: Ecto.Query.t()
def get_context(query, context) do
from(
f in query,
where: ^context in f.context
)
end

@spec get_filters(Ecto.Query.t() | module(), User.t()) :: [t()]
def get_filters(query \\ __MODULE__, %User{id: user_id}) do
query =
Expand Down Expand Up @@ -193,18 +201,22 @@ defmodule Pleroma.Filter do
end
end

@spec compose_regex(User.t() | [t()], format()) :: String.t() | Regex.t() | nil
def compose_regex(user_or_filters, format \\ :postgres)
@spec compose_regex(User.t(), String.t(), format()) :: String.t() | Regex.t() | nil
def compose_regex(user, context, format \\ :postgres)

def compose_regex(%User{} = user, format) do
def compose_regex(%User{} = user, context, format)
when context in ["home", "notifications"] do
__MODULE__
|> get_active()
|> get_irreversible()
|> get_context(context)
|> get_filters(user)
|> compose_regex(format)
|> to_regex(format)
end

def compose_regex([_ | _] = filters, format) do
def compose_regex(_, _, _), do: nil

defp to_regex([_ | _] = filters, format) do
phrases =
filters
|> Enum.map(& &1.phrase)
Expand All @@ -219,5 +231,5 @@ defmodule Pleroma.Filter do
end
end

def compose_regex(_, _), do: nil
defp to_regex(_, _), do: nil
end
4 changes: 2 additions & 2 deletions lib/pleroma/notification.ex
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ defmodule Pleroma.Notification do
end

defp exclude_filtered(query, user) do
case Pleroma.Filter.compose_regex(user) do
case Pleroma.Filter.compose_regex(user, "notifications") do
nil ->
query

Expand Down Expand Up @@ -722,7 +722,7 @@ defmodule Pleroma.Notification do
object.data["actor"] == user.ap_id ->
false

not is_nil(regex = Pleroma.Filter.compose_regex(user, :re)) ->
not is_nil(regex = Pleroma.Filter.compose_regex(user, "notifications", :re)) ->
Regex.match?(regex, object.data["content"])

true ->
Expand Down
8 changes: 2 additions & 6 deletions lib/pleroma/web/activity_pub/activity_pub.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1212,8 +1212,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do

defp restrict_instance(query, _), do: query

defp restrict_filtered(query, %{user: %User{} = user}) do
case Filter.compose_regex(user) do
defp restrict_filtered(query, %{phrase_filtering_user: %User{} = user}) do
case Filter.compose_regex(user, "home") do
nil ->
query

Expand All @@ -1226,10 +1226,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end

defp restrict_filtered(query, %{blocking_user: %User{} = user}) do
restrict_filtered(query, %{user: user})
end

defp restrict_filtered(query, _), do: query

defp restrict_unauthenticated(query, nil) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|> Map.put(:muting_user, user)
|> Map.put(:reply_filtering_user, user)
|> Map.put(:announce_filtering_user, user)
|> Map.put(:phrase_filtering_user, user)
|> Map.put(:user, user)
|> Map.put(:local_only, params[:local])
|> Map.delete(:local)
Expand Down
2 changes: 1 addition & 1 deletion lib/pleroma/web/mastodon_api/websocket_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
def handle_info({:render_with_user, view, template, item, topic}, state) do
user = %User{} = User.get_cached_by_ap_id(state.user.ap_id)

unless Streamer.filtered_by_user?(user, item) do
unless Streamer.filtered_by_user?(topic, user, item) do
message = view.render(template, item, user, topic)
{:push, {:text, message}, %{state | user: user}}
else
Expand Down
31 changes: 26 additions & 5 deletions lib/pleroma/web/streamer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,15 @@ defmodule Pleroma.Web.Streamer do
end
end

def filtered_by_user?(user, item, streamed_type \\ :activity)

def filtered_by_user?(%User{} = user, %Activity{} = item, streamed_type) do
@spec filtered_by_user?(
topic :: String.t(),
User.t(),
Activity.t() | Notification.t(),
:activity | :notification
) :: boolean()
def filtered_by_user?(topic, user, item, streamed_type \\ :activity)

def filtered_by_user?(topic, %User{} = user, %Activity{} = item, streamed_type) do
%{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} =
User.outgoing_relationships_ap_ids(user, [:block, :mute, :reblog_mute])

Expand All @@ -201,6 +207,9 @@ defmodule Pleroma.Web.Streamer do
parent.data["actor"] == user.ap_id),
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
true <- MapSet.disjoint?(recipients, recipient_blocks),
false <-
streamed_type == :activity && topic == "user:" <> to_string(user.id) &&
match_irreversible_filter(user, parent),
%{host: item_host} <- URI.parse(item.actor),
%{host: parent_host} <- URI.parse(parent.data["actor"]),
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host),
Expand All @@ -213,8 +222,8 @@ defmodule Pleroma.Web.Streamer do
end
end

def filtered_by_user?(%User{} = user, %Notification{activity: activity}, _) do
filtered_by_user?(user, activity, :notification)
def filtered_by_user?(topic, %User{} = user, %Notification{activity: activity}, _) do
filtered_by_user?(topic, user, activity, :notification)
end

defp do_stream("direct", item) do
Expand Down Expand Up @@ -401,6 +410,18 @@ defmodule Pleroma.Web.Streamer do
end
end

defp match_irreversible_filter(user, %Object{data: %{"content" => content}}) do
case Pleroma.Filter.compose_regex(user, "home", :re) do
nil ->
false

regex ->
Regex.match?(regex, content)
end
end

defp match_irreversible_filter(_, _), do: false

# In dev/prod the streamer registry is expected to be started, so return true
# In test it is possible to have the registry started for a test so it will check
# In benchmark it will never find the process alive and return false
Expand Down
4 changes: 2 additions & 2 deletions test/pleroma/notification_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ defmodule Pleroma.NotificationTest do
subscriber = insert(:user)

User.subscribe(subscriber, user)
insert(:filter, user: subscriber, phrase: "cofe", hide: true)
insert(:filter, user: subscriber, phrase: "cofe", context: ["notifications"], hide: true)

{:ok, status} = CommonAPI.post(user, %{status: "got cofe?"})

Expand Down Expand Up @@ -1068,7 +1068,7 @@ defmodule Pleroma.NotificationTest do
end

test "it doesn't return notifications about mentions with filtered word", %{user: user} do
insert(:filter, user: user, phrase: "cofe", hide: true)
insert(:filter, user: user, phrase: "cofe", context: ["notifications"], hide: true)
another_user = insert(:user)

{:ok, _activity} = CommonAPI.post(another_user, %{status: "@#{user.nickname} got cofe?"})
Expand Down
43 changes: 36 additions & 7 deletions test/pleroma/web/activity_pub/activity_pub_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
test "doesn't return activities with filtered words" do
user = insert(:user)
user_two = insert(:user)
insert(:filter, user: user, phrase: "test", hide: true)
insert(:filter, user: user, phrase: "test", context: ["home"], hide: true)

{:ok, %{id: id1, data: %{"context" => context}}} = CommonAPI.post(user, %{status: "1"})

Expand All @@ -870,7 +870,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do

activities =
context
|> ActivityPub.fetch_activities_for_context(%{user: user})
|> ActivityPub.fetch_activities_for_context(%{user: user, phrase_filtering_user: user})
|> Enum.map(& &1.id)

assert length(activities) == 4
Expand Down Expand Up @@ -1217,13 +1217,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest 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)
insert(:filter, user: user_two, phrase: "cofe", context: ["home", "public"], hide: true)
insert(:filter, user: user_two, phrase: "ok boomer", context: ["home"], hide: true)
insert(:filter, user: user_two, phrase: "noise", context: ["notification"], hide: true)
insert(:filter, user: user_two, phrase: "test", context: ["home"], hide: false)

params = %{
type: ["Create", "Announce"],
user: user_two
user: user_two,
phrase_filtering_user: user_two
}

{:ok, %{user: user, user_two: user_two, params: params}}
Expand Down Expand Up @@ -1266,6 +1268,32 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert Enum.empty?(activities)
end

test "it does not filter if filter is hide or doesn't have home in context", %{
user: user,
params: params
} do
{:ok, _} = CommonAPI.post(user, %{status: "make some noise"})
{:ok, _} = CommonAPI.post(user, %{status: "test"})

activities = ActivityPub.fetch_activities([], params)

assert Enum.count(activities) == 2
end

test "it returns all statuses in public timeline", %{user: user, user_two: user_two} do
{:ok, _} = CommonAPI.post(user, %{status: "got cofe?"})
{:ok, _} = CommonAPI.post(user, %{status: "test!"})

activities =
ActivityPub.fetch_public_activities(%{
type: ["Create"],
muting_user: user_two,
blocking_user: user_two
})

assert Enum.count(activities) == 2
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?"})
Expand All @@ -1274,7 +1302,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
activities =
ActivityPub.fetch_activities([], %{
type: ["Create", "Announce"],
user: another_user
user: another_user,
phrase_filtering_user: another_user
})

assert Enum.count(activities) == 2
Expand Down
Loading

0 comments on commit a4b5800

Please sign in to comment.