Merge branch 'tusooa/2775-emoji-policy' into 'develop'
EmojiPolicy Closes #2775 See merge request pleroma/pleroma!3842
This commit is contained in:
commit
e38207162b
9 changed files with 768 additions and 12 deletions
1
changelog.d/emoji-policy.add
Normal file
1
changelog.d/emoji-policy.add
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Implement MRF policy to reject or delist according to emojis
|
|
@ -408,6 +408,12 @@ config :pleroma, :mrf_keyword,
|
||||||
federated_timeline_removal: [],
|
federated_timeline_removal: [],
|
||||||
replace: []
|
replace: []
|
||||||
|
|
||||||
|
config :pleroma, :mrf_emoji,
|
||||||
|
remove_url: [],
|
||||||
|
remove_shortcode: [],
|
||||||
|
federated_timeline_removal_url: [],
|
||||||
|
federated_timeline_removal_shortcode: []
|
||||||
|
|
||||||
config :pleroma, :mrf_hashtag,
|
config :pleroma, :mrf_hashtag,
|
||||||
sensitive: ["nsfw"],
|
sensitive: ["nsfw"],
|
||||||
reject: [],
|
reject: [],
|
||||||
|
|
|
@ -261,6 +261,11 @@ Notes:
|
||||||
|
|
||||||
* `follower_nickname`: The name of the bot account to use for following newly discovered users. Using `followbot` or similar is strongly suggested.
|
* `follower_nickname`: The name of the bot account to use for following newly discovered users. Using `followbot` or similar is strongly suggested.
|
||||||
|
|
||||||
|
#### :mrf_emoji
|
||||||
|
* `remove_url`: A list of patterns which result in emoji whose URL matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html).
|
||||||
|
* `remove_shortcode`: A list of patterns which result in emoji whose shortcode matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html).
|
||||||
|
* `federated_timeline_removal_url`: A list of patterns which result in message with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html).
|
||||||
|
* `federated_timeline_removal_shortcode`: A list of patterns which result in message with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html).
|
||||||
|
|
||||||
### :activitypub
|
### :activitypub
|
||||||
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
||||||
|
|
|
@ -42,6 +42,18 @@ defmodule Pleroma.Constants do
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const(status_object_types,
|
||||||
|
do: [
|
||||||
|
"Note",
|
||||||
|
"Question",
|
||||||
|
"Audio",
|
||||||
|
"Video",
|
||||||
|
"Event",
|
||||||
|
"Article",
|
||||||
|
"Page"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
const(updatable_object_types,
|
const(updatable_object_types,
|
||||||
do: [
|
do: [
|
||||||
"Note",
|
"Note",
|
||||||
|
|
281
lib/pleroma/web/activity_pub/mrf/emoji_policy.ex
Normal file
281
lib/pleroma/web/activity_pub/mrf/emoji_policy.ex
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
alias Pleroma.Object.Updater
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF.Utils
|
||||||
|
|
||||||
|
@moduledoc "Reject or force-unlisted emojis with certain URLs or names"
|
||||||
|
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
|
||||||
|
defp config_remove_url do
|
||||||
|
Pleroma.Config.get([:mrf_emoji, :remove_url], [])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp config_remove_shortcode do
|
||||||
|
Pleroma.Config.get([:mrf_emoji, :remove_shortcode], [])
|
||||||
|
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
|
||||||
|
def filter(%{"type" => type, "object" => %{"type" => objtype} = object} = message)
|
||||||
|
when type in ["Create", "Update"] and objtype in Pleroma.Constants.status_object_types() do
|
||||||
|
with {:ok, object} <-
|
||||||
|
Updater.do_with_history(object, fn 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
|
||||||
|
|
||||||
|
@impl Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
def filter(%{"type" => type} = object) when type in Pleroma.Constants.actor_types() do
|
||||||
|
with object <- process_remove(object, :url, config_remove_url()),
|
||||||
|
object <- process_remove(object, :shortcode, config_remove_shortcode()) do
|
||||||
|
{:ok, object}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
def filter(%{"type" => "EmojiReact"} = object) do
|
||||||
|
with {:ok, _} <-
|
||||||
|
matched_emoji_checker(config_remove_url(), config_remove_shortcode()).(object) do
|
||||||
|
{:ok, object}
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
{:reject, "[EmojiPolicy] Rejected for having disallowed emoji"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
def filter(message) do
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp match_string?(string, pattern) when is_binary(pattern) do
|
||||||
|
string == pattern
|
||||||
|
end
|
||||||
|
|
||||||
|
defp match_string?(string, %Regex{} = pattern) do
|
||||||
|
String.match?(string, pattern)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp match_any?(string, patterns) do
|
||||||
|
Enum.any?(patterns, &match_string?(string, &1))
|
||||||
|
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
|
||||||
|
process_remove_impl(object, &url_from_tag/1, &url_from_emoji/1, patterns)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp process_remove(object, :shortcode, patterns) do
|
||||||
|
process_remove_impl(object, &shortcode_from_tag/1, &shortcode_from_emoji/1, patterns)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp process_remove_impl(object, extract_from_tag, extract_from_emoji, patterns) do
|
||||||
|
object =
|
||||||
|
if object["tag"] do
|
||||||
|
Map.put(
|
||||||
|
object,
|
||||||
|
"tag",
|
||||||
|
Enum.filter(
|
||||||
|
object["tag"],
|
||||||
|
fn
|
||||||
|
%{"type" => "Emoji"} = tag ->
|
||||||
|
str = extract_from_tag.(tag)
|
||||||
|
|
||||||
|
if is_binary(str) do
|
||||||
|
not match_any?(str, patterns)
|
||||||
|
else
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
true
|
||||||
|
end
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else
|
||||||
|
object
|
||||||
|
end
|
||||||
|
|
||||||
|
object =
|
||||||
|
if object["emoji"] do
|
||||||
|
Map.put(
|
||||||
|
object,
|
||||||
|
"emoji",
|
||||||
|
object["emoji"]
|
||||||
|
|> Enum.reduce(%{}, fn {name, url} = emoji, acc ->
|
||||||
|
if not match_any?(extract_from_emoji.(emoji), patterns) do
|
||||||
|
Map.put(acc, name, url)
|
||||||
|
else
|
||||||
|
acc
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
else
|
||||||
|
object
|
||||||
|
end
|
||||||
|
|
||||||
|
object
|
||||||
|
end
|
||||||
|
|
||||||
|
defp matched_emoji_checker(urls, shortcodes) do
|
||||||
|
fn object ->
|
||||||
|
if any_emoji_match?(object, &url_from_tag/1, &url_from_emoji/1, urls) or
|
||||||
|
any_emoji_match?(
|
||||||
|
object,
|
||||||
|
&shortcode_from_tag/1,
|
||||||
|
&shortcode_from_emoji/1,
|
||||||
|
shortcodes
|
||||||
|
) do
|
||||||
|
{:matched, nil}
|
||||||
|
else
|
||||||
|
{:ok, %{}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_delist(%{"object" => object, "to" => to, "type" => "Create"} = activity) do
|
||||||
|
check = matched_emoji_checker(config_unlist_url(), config_unlist_shortcode())
|
||||||
|
|
||||||
|
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
|
||||||
|
def describe do
|
||||||
|
mrf_emoji =
|
||||||
|
Pleroma.Config.get(:mrf_emoji, [])
|
||||||
|
|> Enum.map(fn {key, value} ->
|
||||||
|
{key, Enum.map(value, &Utils.describe_regex_or_string/1)}
|
||||||
|
end)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
{:ok, %{mrf_emoji: mrf_emoji}}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_emoji,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.EmojiPolicy",
|
||||||
|
label: "MRF Emoji",
|
||||||
|
description:
|
||||||
|
"Reject or force-unlisted emojis whose URLs or names match a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :remove_url,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: """
|
||||||
|
A list of patterns which result in emoji whose URL matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles.
|
||||||
|
|
||||||
|
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
||||||
|
""",
|
||||||
|
suggestions: ["https://example.org/foo.png", ~r/example.org\/foo/iu]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :remove_shortcode,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: """
|
||||||
|
A list of patterns which result in emoji whose shortcode matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles.
|
||||||
|
|
||||||
|
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
||||||
|
""",
|
||||||
|
suggestions: ["foo", ~r/foo/iu]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :federated_timeline_removal_url,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: """
|
||||||
|
A list of patterns which result in message with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses.
|
||||||
|
|
||||||
|
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
||||||
|
""",
|
||||||
|
suggestions: ["https://example.org/foo.png", ~r/example.org\/foo/iu]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :federated_timeline_removal_shortcode,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: """
|
||||||
|
A list of patterns which result in message with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses.
|
||||||
|
|
||||||
|
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
||||||
|
""",
|
||||||
|
suggestions: ["foo", ~r/foo/iu]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,6 +5,8 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
|
defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF.Utils
|
||||||
|
|
||||||
@moduledoc "Reject or Word-Replace messages with a keyword or regex"
|
@moduledoc "Reject or Word-Replace messages with a keyword or regex"
|
||||||
|
|
||||||
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
@ -128,7 +130,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe do
|
def describe do
|
||||||
# This horror is needed to convert regex sigils to strings
|
|
||||||
mrf_keyword =
|
mrf_keyword =
|
||||||
Pleroma.Config.get(:mrf_keyword, [])
|
Pleroma.Config.get(:mrf_keyword, [])
|
||||||
|> Enum.map(fn {key, value} ->
|
|> Enum.map(fn {key, value} ->
|
||||||
|
@ -136,21 +137,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
|
||||||
Enum.map(value, fn
|
Enum.map(value, fn
|
||||||
{pattern, replacement} ->
|
{pattern, replacement} ->
|
||||||
%{
|
%{
|
||||||
"pattern" =>
|
"pattern" => Utils.describe_regex_or_string(pattern),
|
||||||
if not is_binary(pattern) do
|
|
||||||
inspect(pattern)
|
|
||||||
else
|
|
||||||
pattern
|
|
||||||
end,
|
|
||||||
"replacement" => replacement
|
"replacement" => replacement
|
||||||
}
|
}
|
||||||
|
|
||||||
pattern ->
|
pattern ->
|
||||||
if not is_binary(pattern) do
|
Utils.describe_regex_or_string(pattern)
|
||||||
inspect(pattern)
|
|
||||||
else
|
|
||||||
pattern
|
|
||||||
end
|
|
||||||
end)}
|
end)}
|
||||||
end)
|
end)
|
||||||
|> Enum.into(%{})
|
|> Enum.into(%{})
|
||||||
|
|
15
lib/pleroma/web/activity_pub/mrf/utils.ex
Normal file
15
lib/pleroma/web/activity_pub/mrf/utils.ex
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.Utils do
|
||||||
|
@spec describe_regex_or_string(String.t() | Regex.t()) :: String.t()
|
||||||
|
def describe_regex_or_string(pattern) do
|
||||||
|
# This horror is needed to convert regex sigils to strings
|
||||||
|
if not is_binary(pattern) do
|
||||||
|
inspect(pattern)
|
||||||
|
else
|
||||||
|
pattern
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
425
test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs
Normal file
425
test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs
Normal file
|
@ -0,0 +1,425 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF.EmojiPolicy
|
||||||
|
|
||||||
|
setup do: clear_config(:mrf_emoji)
|
||||||
|
|
||||||
|
setup do
|
||||||
|
clear_config([:mrf_emoji], %{
|
||||||
|
remove_url: [],
|
||||||
|
remove_shortcode: [],
|
||||||
|
federated_timeline_removal_url: [],
|
||||||
|
federated_timeline_removal_shortcode: []
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
@emoji_tags [
|
||||||
|
%{
|
||||||
|
"icon" => %{
|
||||||
|
"type" => "Image",
|
||||||
|
"url" => "https://example.org/emoji/biribiri/mikoto_smile2.png"
|
||||||
|
},
|
||||||
|
"id" => "https://example.org/emoji/biribiri/mikoto_smile2.png",
|
||||||
|
"name" => ":mikoto_smile2:",
|
||||||
|
"type" => "Emoji",
|
||||||
|
"updated" => "1970-01-01T00:00:00Z"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"icon" => %{
|
||||||
|
"type" => "Image",
|
||||||
|
"url" => "https://example.org/emoji/biribiri/mikoto_smile3.png"
|
||||||
|
},
|
||||||
|
"id" => "https://example.org/emoji/biribiri/mikoto_smile3.png",
|
||||||
|
"name" => ":mikoto_smile3:",
|
||||||
|
"type" => "Emoji",
|
||||||
|
"updated" => "1970-01-01T00:00:00Z"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"icon" => %{
|
||||||
|
"type" => "Image",
|
||||||
|
"url" => "https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png"
|
||||||
|
},
|
||||||
|
"id" => "https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png",
|
||||||
|
"name" => ":nekomimi_girl_emoji_007:",
|
||||||
|
"type" => "Emoji",
|
||||||
|
"updated" => "1970-01-01T00:00:00Z"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"icon" => %{
|
||||||
|
"type" => "Image",
|
||||||
|
"url" => "https://example.org/test.png"
|
||||||
|
},
|
||||||
|
"id" => "https://example.org/test.png",
|
||||||
|
"name" => ":test:",
|
||||||
|
"type" => "Emoji",
|
||||||
|
"updated" => "1970-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
@misc_tags [%{"type" => "Placeholder"}]
|
||||||
|
|
||||||
|
@user_data %{
|
||||||
|
"type" => "Person",
|
||||||
|
"id" => "https://example.org/placeholder",
|
||||||
|
"name" => "lol",
|
||||||
|
"tag" => @emoji_tags ++ @misc_tags
|
||||||
|
}
|
||||||
|
|
||||||
|
@status_data %{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Note",
|
||||||
|
"id" => "https://example.org/placeholder",
|
||||||
|
"content" => "lol",
|
||||||
|
"tag" => @emoji_tags ++ @misc_tags,
|
||||||
|
"emoji" => %{
|
||||||
|
"mikoto_smile2" => "https://example.org/emoji/biribiri/mikoto_smile2.png",
|
||||||
|
"mikoto_smile3" => "https://example.org/emoji/biribiri/mikoto_smile3.png",
|
||||||
|
"nekomimi_girl_emoji_007" =>
|
||||||
|
"https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.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"]
|
||||||
|
}
|
||||||
|
|
||||||
|
@emoji_react_data %{
|
||||||
|
"type" => "EmojiReact",
|
||||||
|
"tag" => [@emoji_tags |> Enum.at(3)],
|
||||||
|
"object" => "https://example.org/someobject",
|
||||||
|
"to" => ["https://example.org/self"],
|
||||||
|
"cc" => ["https://example.org/someone"]
|
||||||
|
}
|
||||||
|
|
||||||
|
@emoji_react_data_matching_regex %{
|
||||||
|
"type" => "EmojiReact",
|
||||||
|
"tag" => [@emoji_tags |> Enum.at(1)],
|
||||||
|
"object" => "https://example.org/someobject",
|
||||||
|
"to" => ["https://example.org/self"],
|
||||||
|
"cc" => ["https://example.org/someone"]
|
||||||
|
}
|
||||||
|
|
||||||
|
@emoji_react_data_matching_nothing %{
|
||||||
|
"type" => "EmojiReact",
|
||||||
|
"tag" => [@emoji_tags |> Enum.at(2)],
|
||||||
|
"object" => "https://example.org/someobject",
|
||||||
|
"to" => ["https://example.org/self"],
|
||||||
|
"cc" => ["https://example.org/someone"]
|
||||||
|
}
|
||||||
|
|
||||||
|
@emoji_react_data_unicode %{
|
||||||
|
"type" => "EmojiReact",
|
||||||
|
"content" => "😍",
|
||||||
|
"object" => "https://example.org/someobject",
|
||||||
|
"to" => ["https://example.org/self"],
|
||||||
|
"cc" => ["https://example.org/someone"]
|
||||||
|
}
|
||||||
|
|
||||||
|
describe "remove_url" do
|
||||||
|
setup do
|
||||||
|
clear_config([:mrf_emoji, :remove_url], [
|
||||||
|
"https://example.org/test.png",
|
||||||
|
~r{/biribiri/mikoto_smile[23]\.png},
|
||||||
|
"nekomimi_girl_emoji"
|
||||||
|
])
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "processes user" do
|
||||||
|
{:ok, filtered} = MRF.filter_one(EmojiPolicy, @user_data)
|
||||||
|
|
||||||
|
expected_tags = [@emoji_tags |> Enum.at(2)] ++ @misc_tags
|
||||||
|
|
||||||
|
assert %{"tag" => ^expected_tags} = filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
test "processes status" do
|
||||||
|
{:ok, filtered} = MRF.filter_one(EmojiPolicy, @status_data)
|
||||||
|
|
||||||
|
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}} = filtered
|
||||||
|
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
|
||||||
|
|
||||||
|
test "processes EmojiReact" do
|
||||||
|
assert {:reject, "[EmojiPolicy] Rejected for having disallowed emoji"} ==
|
||||||
|
MRF.filter_one(EmojiPolicy, @emoji_react_data)
|
||||||
|
|
||||||
|
assert {:reject, "[EmojiPolicy] Rejected for having disallowed emoji"} ==
|
||||||
|
MRF.filter_one(EmojiPolicy, @emoji_react_data_matching_regex)
|
||||||
|
|
||||||
|
assert {:ok, @emoji_react_data_matching_nothing} ==
|
||||||
|
MRF.filter_one(EmojiPolicy, @emoji_react_data_matching_nothing)
|
||||||
|
|
||||||
|
assert {:ok, @emoji_react_data_unicode} ==
|
||||||
|
MRF.filter_one(EmojiPolicy, @emoji_react_data_unicode)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "remove_shortcode" do
|
||||||
|
setup do
|
||||||
|
clear_config([:mrf_emoji, :remove_shortcode], [
|
||||||
|
"test",
|
||||||
|
~r{mikoto_s},
|
||||||
|
"nekomimi_girl_emoji"
|
||||||
|
])
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "processes user" do
|
||||||
|
{:ok, filtered} = MRF.filter_one(EmojiPolicy, @user_data)
|
||||||
|
|
||||||
|
expected_tags = [@emoji_tags |> Enum.at(2)] ++ @misc_tags
|
||||||
|
|
||||||
|
assert %{"tag" => ^expected_tags} = filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
test "processes status" do
|
||||||
|
{:ok, filtered} = MRF.filter_one(EmojiPolicy, @status_data)
|
||||||
|
|
||||||
|
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}} = filtered
|
||||||
|
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
|
||||||
|
|
||||||
|
test "processes EmojiReact" do
|
||||||
|
assert {:reject, "[EmojiPolicy] Rejected for having disallowed emoji"} ==
|
||||||
|
MRF.filter_one(EmojiPolicy, @emoji_react_data)
|
||||||
|
|
||||||
|
assert {:reject, "[EmojiPolicy] Rejected for having disallowed emoji"} ==
|
||||||
|
MRF.filter_one(EmojiPolicy, @emoji_react_data_matching_regex)
|
||||||
|
|
||||||
|
assert {:ok, @emoji_react_data_matching_nothing} ==
|
||||||
|
MRF.filter_one(EmojiPolicy, @emoji_react_data_matching_nothing)
|
||||||
|
|
||||||
|
assert {:ok, @emoji_react_data_unicode} ==
|
||||||
|
MRF.filter_one(EmojiPolicy, @emoji_react_data_unicode)
|
||||||
|
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
|
||||||
|
|
||||||
|
describe "edge cases" do
|
||||||
|
setup do
|
||||||
|
clear_config([:mrf_emoji, :remove_url], [
|
||||||
|
"https://example.org/test.png",
|
||||||
|
~r{/biribiri/mikoto_smile[23]\.png},
|
||||||
|
"nekomimi_girl_emoji"
|
||||||
|
])
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "non-statuses" do
|
||||||
|
answer = @status_data |> put_in(["object", "type"], "Answer")
|
||||||
|
{:ok, filtered} = MRF.filter_one(EmojiPolicy, answer)
|
||||||
|
|
||||||
|
assert filtered == answer
|
||||||
|
end
|
||||||
|
|
||||||
|
test "without tag" do
|
||||||
|
status = @status_data |> Map.put("object", Map.drop(@status_data["object"], ["tag"]))
|
||||||
|
{:ok, filtered} = MRF.filter_one(EmojiPolicy, status)
|
||||||
|
|
||||||
|
refute Map.has_key?(filtered["object"], "tag")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "without emoji" do
|
||||||
|
status = @status_data |> Map.put("object", Map.drop(@status_data["object"], ["emoji"]))
|
||||||
|
{:ok, filtered} = MRF.filter_one(EmojiPolicy, status)
|
||||||
|
|
||||||
|
refute Map.has_key?(filtered["object"], "emoji")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
19
test/pleroma/web/activity_pub/mrf/utils_test.exs
Normal file
19
test/pleroma/web/activity_pub/mrf/utils_test.exs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.UtilsTest do
|
||||||
|
use Pleroma.DataCase, async: true
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF.Utils
|
||||||
|
|
||||||
|
describe "describe_regex_or_string/1" do
|
||||||
|
test "describes regex" do
|
||||||
|
assert "~r/foo/i" == Utils.describe_regex_or_string(~r/foo/i)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns string as-is" do
|
||||||
|
assert "foo" == Utils.describe_regex_or_string("foo")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue