EmojiPolicy: Implement delist
This commit is contained in:
parent
80ce6482f6
commit
7eb8abf7bb
|
@ -5,6 +5,8 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
|
defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
alias Pleroma.Object.Updater
|
||||||
|
|
||||||
@moduledoc "Reject or force-unlisted emojis with certain URLs or names"
|
@moduledoc "Reject or force-unlisted emojis with certain URLs or names"
|
||||||
|
|
||||||
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
@ -17,12 +19,31 @@ defp config_remove_shortcode do
|
||||||
Pleroma.Config.get([:mrf_emoji, :remove_shortcode], [])
|
Pleroma.Config.get([:mrf_emoji, :remove_shortcode], [])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp config_unlist_url do
|
||||||
|
Pleroma.Config.get([:mrf_emoji, :federated_timeline_removal_url], [])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp config_unlist_shortcode do
|
||||||
|
Pleroma.Config.get([:mrf_emoji, :federated_timeline_removal_shortcode], [])
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
def history_awareness, do: :manual
|
||||||
|
|
||||||
@impl Pleroma.Web.ActivityPub.MRF.Policy
|
@impl Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
def filter(%{"type" => type, "object" => %{} = object} = message)
|
def filter(%{"type" => type, "object" => %{} = object} = message)
|
||||||
when type in ["Create", "Update"] do
|
when type in ["Create", "Update"] do
|
||||||
with object <- process_remove(object, :url, config_remove_url()),
|
with {:ok, object} <-
|
||||||
object <- process_remove(object, :shortcode, config_remove_shortcode()) do
|
Updater.do_with_history(object, fn object ->
|
||||||
{:ok, Map.put(message, "object", object)}
|
{:ok, process_remove(object, :url, config_remove_url())}
|
||||||
|
end),
|
||||||
|
{:ok, object} <-
|
||||||
|
Updater.do_with_history(object, fn object ->
|
||||||
|
{:ok, process_remove(object, :shortcode, config_remove_shortcode())}
|
||||||
|
end),
|
||||||
|
activity <- Map.put(message, "object", object),
|
||||||
|
activity <- maybe_delist(activity) do
|
||||||
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -51,28 +72,22 @@ defp match_any?(string, patterns) do
|
||||||
Enum.any?(patterns, &match_string?(string, &1))
|
Enum.any?(patterns, &match_string?(string, &1))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp url_from_tag(%{"icon" => %{"url" => url}}), do: url
|
||||||
|
defp url_from_tag(_), do: nil
|
||||||
|
|
||||||
|
defp url_from_emoji({_name, url}), do: url
|
||||||
|
|
||||||
|
defp shortcode_from_tag(%{"name" => name}) when is_binary(name), do: String.trim(name, ":")
|
||||||
|
defp shortcode_from_tag(_), do: nil
|
||||||
|
|
||||||
|
defp shortcode_from_emoji({name, _url}), do: name
|
||||||
|
|
||||||
defp process_remove(object, :url, patterns) do
|
defp process_remove(object, :url, patterns) do
|
||||||
process_remove_impl(
|
process_remove_impl(object, &url_from_tag/1, &url_from_emoji/1, patterns)
|
||||||
object,
|
|
||||||
fn
|
|
||||||
%{"icon" => %{"url" => url}} -> url
|
|
||||||
_ -> nil
|
|
||||||
end,
|
|
||||||
fn {_name, url} -> url end,
|
|
||||||
patterns
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp process_remove(object, :shortcode, patterns) do
|
defp process_remove(object, :shortcode, patterns) do
|
||||||
process_remove_impl(
|
process_remove_impl(object, &shortcode_from_tag/1, &shortcode_from_emoji/1, patterns)
|
||||||
object,
|
|
||||||
fn
|
|
||||||
%{"name" => name} when is_binary(name) -> String.trim(name, ":")
|
|
||||||
_ -> nil
|
|
||||||
end,
|
|
||||||
fn {name, _url} -> name end,
|
|
||||||
patterns
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp process_remove_impl(object, extract_from_tag, extract_from_emoji, patterns) do
|
defp process_remove_impl(object, extract_from_tag, extract_from_emoji, patterns) do
|
||||||
|
@ -118,6 +133,66 @@ defp process_remove_impl(object, extract_from_tag, extract_from_emoji, patterns)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_delist(%{"object" => object, "to" => to, "type" => "Create"} = activity) do
|
||||||
|
check = fn object ->
|
||||||
|
if any_emoji_match?(object, &url_from_tag/1, &url_from_emoji/1, config_unlist_url()) or
|
||||||
|
any_emoji_match?(
|
||||||
|
object,
|
||||||
|
&shortcode_from_tag/1,
|
||||||
|
&shortcode_from_emoji/1,
|
||||||
|
config_unlist_shortcode()
|
||||||
|
) do
|
||||||
|
{:should_delist, nil}
|
||||||
|
else
|
||||||
|
{:ok, %{}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
should_delist? = fn object ->
|
||||||
|
with {:ok, _} <- Pleroma.Object.Updater.do_with_history(object, check) do
|
||||||
|
false
|
||||||
|
else
|
||||||
|
_ -> true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Pleroma.Constants.as_public() in to and should_delist?.(object) do
|
||||||
|
to = List.delete(to, Pleroma.Constants.as_public())
|
||||||
|
cc = [Pleroma.Constants.as_public() | activity["cc"] || []]
|
||||||
|
|
||||||
|
activity
|
||||||
|
|> Map.put("to", to)
|
||||||
|
|> Map.put("cc", cc)
|
||||||
|
else
|
||||||
|
activity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_delist(activity), do: activity
|
||||||
|
|
||||||
|
defp any_emoji_match?(object, extract_from_tag, extract_from_emoji, patterns) do
|
||||||
|
Kernel.||(
|
||||||
|
Enum.any?(
|
||||||
|
object["tag"],
|
||||||
|
fn
|
||||||
|
%{"type" => "Emoji"} = tag ->
|
||||||
|
str = extract_from_tag.(tag)
|
||||||
|
|
||||||
|
if is_binary(str) do
|
||||||
|
match_any?(str, patterns)
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
),
|
||||||
|
object["emoji"]
|
||||||
|
|> Enum.any?(fn emoji -> match_any?(extract_from_emoji.(emoji), patterns) end)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
@impl Pleroma.Web.ActivityPub.MRF.Policy
|
@impl Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
def describe do
|
def describe do
|
||||||
# This horror is needed to convert regex sigils to strings
|
# This horror is needed to convert regex sigils to strings
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do
|
defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
alias Pleroma.Web.ActivityPub.MRF
|
alias Pleroma.Web.ActivityPub.MRF
|
||||||
alias Pleroma.Web.ActivityPub.MRF.EmojiPolicy
|
alias Pleroma.Web.ActivityPub.MRF.EmojiPolicy
|
||||||
|
|
||||||
|
@ -84,8 +86,27 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do
|
||||||
"nekomimi_girl_emoji_007" =>
|
"nekomimi_girl_emoji_007" =>
|
||||||
"https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png",
|
"https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png",
|
||||||
"test" => "https://example.org/test.png"
|
"test" => "https://example.org/test.png"
|
||||||
|
},
|
||||||
|
"to" => ["https://example.org/self", Pleroma.Constants.as_public()],
|
||||||
|
"cc" => ["https://example.org/someone"]
|
||||||
|
},
|
||||||
|
"to" => ["https://example.org/self", Pleroma.Constants.as_public()],
|
||||||
|
"cc" => ["https://example.org/someone"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@status_data_with_history %{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" =>
|
||||||
|
@status_data["object"]
|
||||||
|
|> Map.merge(%{
|
||||||
|
"formerRepresentations" => %{
|
||||||
|
"type" => "OrderedCollection",
|
||||||
|
"orderedItems" => [@status_data["object"] |> Map.put("content", "older")],
|
||||||
|
"totalItems" => 1
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
"to" => ["https://example.org/self", Pleroma.Constants.as_public()],
|
||||||
|
"cc" => ["https://example.org/someone"]
|
||||||
}
|
}
|
||||||
|
|
||||||
describe "remove_url" do
|
describe "remove_url" do
|
||||||
|
@ -119,6 +140,49 @@ test "processes status" do
|
||||||
|
|
||||||
assert %{"object" => %{"tag" => ^expected_tags, "emoji" => ^expected_emoji}} = filtered
|
assert %{"object" => %{"tag" => ^expected_tags, "emoji" => ^expected_emoji}} = filtered
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "processes status with history" do
|
||||||
|
{:ok, filtered} = MRF.filter_one(EmojiPolicy, @status_data_with_history)
|
||||||
|
|
||||||
|
expected_tags = [@emoji_tags |> Enum.at(2)] ++ @misc_tags
|
||||||
|
|
||||||
|
expected_emoji = %{
|
||||||
|
"nekomimi_girl_emoji_007" =>
|
||||||
|
"https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"object" => %{
|
||||||
|
"tag" => ^expected_tags,
|
||||||
|
"emoji" => ^expected_emoji,
|
||||||
|
"formerRepresentations" => %{"orderedItems" => [item]}
|
||||||
|
}
|
||||||
|
} = filtered
|
||||||
|
|
||||||
|
assert %{"tag" => ^expected_tags, "emoji" => ^expected_emoji} = item
|
||||||
|
end
|
||||||
|
|
||||||
|
test "processes updates" do
|
||||||
|
{:ok, filtered} =
|
||||||
|
MRF.filter_one(EmojiPolicy, @status_data_with_history |> Map.put("type", "Update"))
|
||||||
|
|
||||||
|
expected_tags = [@emoji_tags |> Enum.at(2)] ++ @misc_tags
|
||||||
|
|
||||||
|
expected_emoji = %{
|
||||||
|
"nekomimi_girl_emoji_007" =>
|
||||||
|
"https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"object" => %{
|
||||||
|
"tag" => ^expected_tags,
|
||||||
|
"emoji" => ^expected_emoji,
|
||||||
|
"formerRepresentations" => %{"orderedItems" => [item]}
|
||||||
|
}
|
||||||
|
} = filtered
|
||||||
|
|
||||||
|
assert %{"tag" => ^expected_tags, "emoji" => ^expected_emoji} = item
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "remove_shortcode" do
|
describe "remove_shortcode" do
|
||||||
|
@ -152,5 +216,117 @@ test "processes status" do
|
||||||
|
|
||||||
assert %{"object" => %{"tag" => ^expected_tags, "emoji" => ^expected_emoji}} = filtered
|
assert %{"object" => %{"tag" => ^expected_tags, "emoji" => ^expected_emoji}} = filtered
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "processes status with history" do
|
||||||
|
{:ok, filtered} = MRF.filter_one(EmojiPolicy, @status_data_with_history)
|
||||||
|
|
||||||
|
expected_tags = [@emoji_tags |> Enum.at(2)] ++ @misc_tags
|
||||||
|
|
||||||
|
expected_emoji = %{
|
||||||
|
"nekomimi_girl_emoji_007" =>
|
||||||
|
"https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"object" => %{
|
||||||
|
"tag" => ^expected_tags,
|
||||||
|
"emoji" => ^expected_emoji,
|
||||||
|
"formerRepresentations" => %{"orderedItems" => [item]}
|
||||||
|
}
|
||||||
|
} = filtered
|
||||||
|
|
||||||
|
assert %{"tag" => ^expected_tags, "emoji" => ^expected_emoji} = item
|
||||||
|
end
|
||||||
|
|
||||||
|
test "processes updates" do
|
||||||
|
{:ok, filtered} =
|
||||||
|
MRF.filter_one(EmojiPolicy, @status_data_with_history |> Map.put("type", "Update"))
|
||||||
|
|
||||||
|
expected_tags = [@emoji_tags |> Enum.at(2)] ++ @misc_tags
|
||||||
|
|
||||||
|
expected_emoji = %{
|
||||||
|
"nekomimi_girl_emoji_007" =>
|
||||||
|
"https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"object" => %{
|
||||||
|
"tag" => ^expected_tags,
|
||||||
|
"emoji" => ^expected_emoji,
|
||||||
|
"formerRepresentations" => %{"orderedItems" => [item]}
|
||||||
|
}
|
||||||
|
} = filtered
|
||||||
|
|
||||||
|
assert %{"tag" => ^expected_tags, "emoji" => ^expected_emoji} = item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "federated_timeline_removal_url" do
|
||||||
|
setup do
|
||||||
|
clear_config([:mrf_emoji, :federated_timeline_removal_url], [
|
||||||
|
"https://example.org/test.png",
|
||||||
|
~r{/biribiri/mikoto_smile[23]\.png},
|
||||||
|
"nekomimi_girl_emoji"
|
||||||
|
])
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "processes status" do
|
||||||
|
{:ok, filtered} = MRF.filter_one(EmojiPolicy, @status_data)
|
||||||
|
|
||||||
|
expected_tags = @status_data["object"]["tag"]
|
||||||
|
expected_emoji = @status_data["object"]["emoji"]
|
||||||
|
|
||||||
|
expected_to = ["https://example.org/self"]
|
||||||
|
expected_cc = [Pleroma.Constants.as_public(), "https://example.org/someone"]
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"to" => ^expected_to,
|
||||||
|
"cc" => ^expected_cc,
|
||||||
|
"object" => %{"tag" => ^expected_tags, "emoji" => ^expected_emoji}
|
||||||
|
} = filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ignore updates" do
|
||||||
|
{:ok, filtered} = MRF.filter_one(EmojiPolicy, @status_data |> Map.put("type", "Update"))
|
||||||
|
|
||||||
|
expected_tags = @status_data["object"]["tag"]
|
||||||
|
expected_emoji = @status_data["object"]["emoji"]
|
||||||
|
|
||||||
|
expected_to = ["https://example.org/self", Pleroma.Constants.as_public()]
|
||||||
|
expected_cc = ["https://example.org/someone"]
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"to" => ^expected_to,
|
||||||
|
"cc" => ^expected_cc,
|
||||||
|
"object" => %{"tag" => ^expected_tags, "emoji" => ^expected_emoji}
|
||||||
|
} = filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
test "processes status with history" do
|
||||||
|
status =
|
||||||
|
@status_data_with_history
|
||||||
|
|> put_in(["object", "tag"], @misc_tags)
|
||||||
|
|> put_in(["object", "emoji"], %{})
|
||||||
|
|
||||||
|
{:ok, filtered} = MRF.filter_one(EmojiPolicy, status)
|
||||||
|
|
||||||
|
expected_tags = @status_data["object"]["tag"]
|
||||||
|
expected_emoji = @status_data["object"]["emoji"]
|
||||||
|
|
||||||
|
expected_to = ["https://example.org/self"]
|
||||||
|
expected_cc = [Pleroma.Constants.as_public(), "https://example.org/someone"]
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"to" => ^expected_to,
|
||||||
|
"cc" => ^expected_cc,
|
||||||
|
"object" => %{
|
||||||
|
"formerRepresentations" => %{
|
||||||
|
"orderedItems" => [%{"tag" => ^expected_tags, "emoji" => ^expected_emoji}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} = filtered
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue