emoji_policy.ex (8626B)
- # 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