logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git

emoji_policy.ex (8626B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
  5. require Pleroma.Constants
  6. alias Pleroma.Object.Updater
  7. alias Pleroma.Web.ActivityPub.MRF.Utils
  8. @moduledoc "Reject or force-unlisted emojis with certain URLs or names"
  9. @behaviour Pleroma.Web.ActivityPub.MRF.Policy
  10. defp config_remove_url do
  11. Pleroma.Config.get([:mrf_emoji, :remove_url], [])
  12. end
  13. defp config_remove_shortcode do
  14. Pleroma.Config.get([:mrf_emoji, :remove_shortcode], [])
  15. end
  16. defp config_unlist_url do
  17. Pleroma.Config.get([:mrf_emoji, :federated_timeline_removal_url], [])
  18. end
  19. defp config_unlist_shortcode do
  20. Pleroma.Config.get([:mrf_emoji, :federated_timeline_removal_shortcode], [])
  21. end
  22. @impl Pleroma.Web.ActivityPub.MRF.Policy
  23. def history_awareness, do: :manual
  24. @impl Pleroma.Web.ActivityPub.MRF.Policy
  25. def filter(%{"type" => type, "object" => %{"type" => objtype} = object} = message)
  26. when type in ["Create", "Update"] and objtype in Pleroma.Constants.status_object_types() do
  27. with {:ok, object} <-
  28. Updater.do_with_history(object, fn object ->
  29. {:ok, process_remove(object, :url, config_remove_url())}
  30. end),
  31. {:ok, object} <-
  32. Updater.do_with_history(object, fn object ->
  33. {:ok, process_remove(object, :shortcode, config_remove_shortcode())}
  34. end),
  35. activity <- Map.put(message, "object", object),
  36. activity <- maybe_delist(activity) do
  37. {:ok, activity}
  38. end
  39. end
  40. @impl Pleroma.Web.ActivityPub.MRF.Policy
  41. def filter(%{"type" => type} = object) when type in Pleroma.Constants.actor_types() do
  42. with object <- process_remove(object, :url, config_remove_url()),
  43. object <- process_remove(object, :shortcode, config_remove_shortcode()) do
  44. {:ok, object}
  45. end
  46. end
  47. @impl Pleroma.Web.ActivityPub.MRF.Policy
  48. def filter(%{"type" => "EmojiReact"} = object) do
  49. with {:ok, _} <-
  50. matched_emoji_checker(config_remove_url(), config_remove_shortcode()).(object) do
  51. {:ok, object}
  52. else
  53. _ ->
  54. {:reject, "[EmojiPolicy] Rejected for having disallowed emoji"}
  55. end
  56. end
  57. @impl Pleroma.Web.ActivityPub.MRF.Policy
  58. def filter(message) do
  59. {:ok, message}
  60. end
  61. defp match_string?(string, pattern) when is_binary(pattern) do
  62. string == pattern
  63. end
  64. defp match_string?(string, %Regex{} = pattern) do
  65. String.match?(string, pattern)
  66. end
  67. defp match_any?(string, patterns) do
  68. Enum.any?(patterns, &match_string?(string, &1))
  69. end
  70. defp url_from_tag(%{"icon" => %{"url" => url}}), do: url
  71. defp url_from_tag(_), do: nil
  72. defp url_from_emoji({_name, url}), do: url
  73. defp shortcode_from_tag(%{"name" => name}) when is_binary(name), do: String.trim(name, ":")
  74. defp shortcode_from_tag(_), do: nil
  75. defp shortcode_from_emoji({name, _url}), do: name
  76. defp process_remove(object, :url, patterns) do
  77. process_remove_impl(object, &url_from_tag/1, &url_from_emoji/1, patterns)
  78. end
  79. defp process_remove(object, :shortcode, patterns) do
  80. process_remove_impl(object, &shortcode_from_tag/1, &shortcode_from_emoji/1, patterns)
  81. end
  82. defp process_remove_impl(object, extract_from_tag, extract_from_emoji, patterns) do
  83. object =
  84. if object["tag"] do
  85. Map.put(
  86. object,
  87. "tag",
  88. Enum.filter(
  89. object["tag"],
  90. fn
  91. %{"type" => "Emoji"} = tag ->
  92. str = extract_from_tag.(tag)
  93. if is_binary(str) do
  94. not match_any?(str, patterns)
  95. else
  96. true
  97. end
  98. _ ->
  99. true
  100. end
  101. )
  102. )
  103. else
  104. object
  105. end
  106. object =
  107. if object["emoji"] do
  108. Map.put(
  109. object,
  110. "emoji",
  111. object["emoji"]
  112. |> Enum.reduce(%{}, fn {name, url} = emoji, acc ->
  113. if not match_any?(extract_from_emoji.(emoji), patterns) do
  114. Map.put(acc, name, url)
  115. else
  116. acc
  117. end
  118. end)
  119. )
  120. else
  121. object
  122. end
  123. object
  124. end
  125. defp matched_emoji_checker(urls, shortcodes) do
  126. fn object ->
  127. if any_emoji_match?(object, &url_from_tag/1, &url_from_emoji/1, urls) or
  128. any_emoji_match?(
  129. object,
  130. &shortcode_from_tag/1,
  131. &shortcode_from_emoji/1,
  132. shortcodes
  133. ) do
  134. {:matched, nil}
  135. else
  136. {:ok, %{}}
  137. end
  138. end
  139. end
  140. defp maybe_delist(%{"object" => object, "to" => to, "type" => "Create"} = activity) do
  141. check = matched_emoji_checker(config_unlist_url(), config_unlist_shortcode())
  142. should_delist? = fn object ->
  143. with {:ok, _} <- Pleroma.Object.Updater.do_with_history(object, check) do
  144. false
  145. else
  146. _ -> true
  147. end
  148. end
  149. if Pleroma.Constants.as_public() in to and should_delist?.(object) do
  150. to = List.delete(to, Pleroma.Constants.as_public())
  151. cc = [Pleroma.Constants.as_public() | activity["cc"] || []]
  152. activity
  153. |> Map.put("to", to)
  154. |> Map.put("cc", cc)
  155. else
  156. activity
  157. end
  158. end
  159. defp maybe_delist(activity), do: activity
  160. defp any_emoji_match?(object, extract_from_tag, extract_from_emoji, patterns) do
  161. Kernel.||(
  162. Enum.any?(
  163. object["tag"] || [],
  164. fn
  165. %{"type" => "Emoji"} = tag ->
  166. str = extract_from_tag.(tag)
  167. if is_binary(str) do
  168. match_any?(str, patterns)
  169. else
  170. false
  171. end
  172. _ ->
  173. false
  174. end
  175. ),
  176. (object["emoji"] || [])
  177. |> Enum.any?(fn emoji -> match_any?(extract_from_emoji.(emoji), patterns) end)
  178. )
  179. end
  180. @impl Pleroma.Web.ActivityPub.MRF.Policy
  181. def describe do
  182. mrf_emoji =
  183. Pleroma.Config.get(:mrf_emoji, [])
  184. |> Enum.map(fn {key, value} ->
  185. {key, Enum.map(value, &Utils.describe_regex_or_string/1)}
  186. end)
  187. |> Enum.into(%{})
  188. {:ok, %{mrf_emoji: mrf_emoji}}
  189. end
  190. @impl Pleroma.Web.ActivityPub.MRF.Policy
  191. def config_description do
  192. %{
  193. key: :mrf_emoji,
  194. related_policy: "Pleroma.Web.ActivityPub.MRF.EmojiPolicy",
  195. label: "MRF Emoji",
  196. description:
  197. "Reject or force-unlisted emojis whose URLs or names match a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).",
  198. children: [
  199. %{
  200. key: :remove_url,
  201. type: {:list, :string},
  202. description: """
  203. 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.
  204. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
  205. """,
  206. suggestions: ["https://example.org/foo.png", ~r/example.org\/foo/iu]
  207. },
  208. %{
  209. key: :remove_shortcode,
  210. type: {:list, :string},
  211. description: """
  212. 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.
  213. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
  214. """,
  215. suggestions: ["foo", ~r/foo/iu]
  216. },
  217. %{
  218. key: :federated_timeline_removal_url,
  219. type: {:list, :string},
  220. description: """
  221. 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.
  222. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
  223. """,
  224. suggestions: ["https://example.org/foo.png", ~r/example.org\/foo/iu]
  225. },
  226. %{
  227. key: :federated_timeline_removal_shortcode,
  228. type: {:list, :string},
  229. description: """
  230. 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.
  231. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
  232. """,
  233. suggestions: ["foo", ~r/foo/iu]
  234. }
  235. ]
  236. }
  237. end
  238. end