diff --git a/lib/pleroma/web/activity_pub/mrf/block_notification.ex b/lib/pleroma/web/activity_pub/mrf/block_notification.ex
new file mode 100644
index 000000000..b3350a338
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/block_notification.ex
@@ -0,0 +1,69 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.BlockNotification do
+ @moduledoc "Notify local users upon remote block."
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+
+ defp is_block_or_unblock(%{"type" => "Block", "object" => object}),
+ do: {true, "blocked", object}
+
+ defp is_block_or_unblock(%{
+ "type" => "Undo",
+ "object" => %{"type" => "Block", "object" => object}
+ }),
+ do: {true, "unblocked", object}
+
+ defp is_block_or_unblock(_), do: {false, nil, nil}
+
+ defp is_remote_or_displaying_local?(%User{local: false}), do: true
+
+ defp is_remote_or_displaying_local?(_), do: true
+
+ @impl true
+ def filter(message) do
+
+ with {true, action, object} <- is_block_or_unblock(message),
+ %User{} = actor <- User.get_cached_by_ap_id(message["actor"]),
+ %User{} = recipient <- User.get_cached_by_ap_id(object),
+ true <- recipient.local,
+ true <- is_remote_or_displaying_local?(actor),
+ false <- User.blocks_user?(recipient, actor) do
+
+ # Create /opt/pleroma/logs/ with write perms for user pleroma
+ # Make a cron job to delete the log file every hour or whatever
+ # Not my problem
+ log_file = "/opt/pleroma/logs/blocks.log"
+ bot_user = "cockblock"
+
+ log_contents = if File.exists?(log_file) do
+ File.read!(log_file)
+ else
+ ""
+ end
+
+ logged_blocks = String.split(log_contents, "\n")
+
+ actor_name = (fn actor_uri -> Path.basename(actor_uri.path) <> "@" <> actor_uri.authority end).(URI.parse(message["actor"]))
+ log_entry = actor_name <> ":" <> action
+
+ unless Enum.member?(logged_blocks, log_entry) do
+ File.write!(log_file, log_entry <> "\n", [:append])
+ _reply =
+ CommonAPI.post(User.get_by_nickname(bot_user), %{
+ status: "@" <> recipient.nickname <> " you have been " <> action <> " by @" <> actor_name <> " (" <> actor_name <> ")",
+ visibility: "unlisted"
+ })
+ end
+ end
+
+ {:ok, message}
+ end
+
+ @impl true
+ def describe, do: {:ok, %{}}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/change_react_to_like.ex b/lib/pleroma/web/activity_pub/mrf/change_react_to_like.ex
new file mode 100644
index 000000000..0275db93d
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/change_react_to_like.ex
@@ -0,0 +1,47 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.ChangeReactstoLikes do
+ require Logger
+
+ @moduledoc "Changes specified EmojiReacts into a Like"
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ defp is_remote(host) do
+ my_host = Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
+ my_host != host
+ end
+
+ @impl true
+ @spec filter(any) :: {:ok, any}
+ def filter(%{"type" => "EmojiReact"} = object) do
+ actor = object["actor"]
+ host = URI.parse(actor).host
+
+ if is_remote(host) do
+ react = object["content"]
+
+ # TODO: make this pull from config
+ if react in ["👍", "👎", "❤️", "😆", "😮", "😢", "😩", "😭", "🔥", "⭐"] do
+ Logger.info("MRF.ChangeReactstoLikes: Changing #{inspect(react)} to a Like")
+
+ object =
+ object
+ |> Map.put("type", "Like")
+
+ {:ok, object}
+ else
+ {:ok, object}
+ end
+ else
+ {:ok, object}
+ end
+ end
+
+ @impl true
+ def filter(object), do: {:ok, object}
+
+ @impl true
+ def describe, do: {:ok, %{}}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex
new file mode 100644
index 000000000..f28fabc5d
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex
@@ -0,0 +1,148 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
+ require Pleroma.Constants
+
+ alias Pleroma.Formatter
+ alias Pleroma.Object
+ alias Pleroma.User
+
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ defp do_extract({:a, attrs, _}, acc) do
+ if Enum.find(attrs, fn {name, value} ->
+ name == "class" && value in ["mention", "u-url mention", "mention u-url"]
+ end) do
+ href = Enum.find(attrs, fn {name, _} -> name == "href" end) |> elem(1)
+ acc ++ [href]
+ else
+ acc
+ end
+ end
+
+ defp do_extract({_, _, children}, acc) do
+ do_extract(children, acc)
+ end
+
+ defp do_extract(nodes, acc) when is_list(nodes) do
+ Enum.reduce(nodes, acc, fn node, acc -> do_extract(node, acc) end)
+ end
+
+ defp do_extract(_, acc), do: acc
+
+ defp extract_mention_uris_from_content(content) do
+ {:ok, tree} = :fast_html.decode(content, format: [:html_atoms])
+ do_extract(tree, [])
+ end
+
+ defp get_replied_to_user(%{"inReplyTo" => in_reply_to}) do
+ case Object.normalize(in_reply_to, fetch: false) do
+ %Object{data: %{"actor" => actor}} -> User.get_cached_by_ap_id(actor)
+ _ -> nil
+ end
+ end
+
+ defp get_replied_to_user(_object), do: nil
+
+ # Ensure the replied-to user is sorted to the left
+ defp sort_replied_user([%User{id: user_id} | _] = users, %User{id: user_id}), do: users
+
+ defp sort_replied_user(users, %User{id: user_id} = user) do
+ if Enum.find(users, fn u -> u.id == user_id end) do
+ users = Enum.reject(users, fn u -> u.id == user_id end)
+ [user | users]
+ else
+ users
+ end
+ end
+
+ defp sort_replied_user(users, _), do: users
+
+ # Drop constants and the actor's own AP ID
+ defp clean_recipients(recipients, object) do
+ Enum.reject(recipients, fn ap_id ->
+ ap_id in [
+ object["object"]["actor"],
+ Pleroma.Constants.as_public(),
+ Pleroma.Web.ActivityPub.Utils.as_local_public()
+ ]
+ end)
+ end
+
+ defp is_remote(host) do
+ my_host = Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
+ my_host != host
+ end
+
+ @impl true
+ def filter(
+ %{
+ "type" => "Create",
+ "object" => %{"type" => "Note", "to" => to, "inReplyTo" => in_reply_to}
+ } = object
+ )
+ when is_list(to) and is_binary(in_reply_to) do
+ actor = object["object"]["actor"]
+ host = URI.parse(actor).host
+
+ if is_remote(host) do
+ # image-only posts from pleroma apparently reach this MRF without the content field
+ content = object["object"]["content"] || ""
+
+ # Get the replied-to user for sorting
+ replied_to_user = get_replied_to_user(object["object"])
+
+ mention_users =
+ to
+ |> clean_recipients(object)
+ |> Enum.map(&User.get_cached_by_ap_id/1)
+ |> Enum.reject(&is_nil/1)
+ |> sort_replied_user(replied_to_user)
+
+ explicitly_mentioned_uris = extract_mention_uris_from_content(content)
+
+ if Enum.empty?(explicitly_mentioned_uris) do
+ added_mentions =
+ Enum.reduce(mention_users, "", fn %User{ap_id: uri} = user, acc ->
+ unless uri in explicitly_mentioned_uris do
+ acc <> Formatter.mention_from_user(user, %{mentions_format: :compact}) <> " "
+ else
+ acc
+ end
+ end)
+
+ recipients_inline =
+ if added_mentions != "",
+ do: "#{added_mentions}",
+ else: ""
+
+ content =
+ cond do
+ # For Markdown posts, insert the mentions inside the first
tag
+ recipients_inline != "" && String.starts_with?(content, "
") ->
+ "
" <> recipients_inline <> String.trim_leading(content, "
")
+
+ recipients_inline != "" ->
+ recipients_inline <> content
+
+ true ->
+ content
+ end
+
+ {:ok, put_in(object["object"]["content"], content)}
+ else
+ {:ok, object}
+ end
+ else
+ {:ok, object}
+ end
+ end
+
+ @impl true
+ def filter(object), do: {:ok, object}
+
+ @impl true
+ def describe, do: {:ok, %{}}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/no_incoming_deletes.ex b/lib/pleroma/web/activity_pub/mrf/no_incoming_deletes.ex
new file mode 100644
index 000000000..ecf1976f6
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/no_incoming_deletes.ex
@@ -0,0 +1,29 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.NoIncomingDeletes do
+ @moduledoc "Reject remote deletes."
+
+ require Logger
+
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+ @impl true
+ def filter(%{"type" => "Delete", "actor" => actor} = object) do
+ actor_info = URI.parse(actor)
+ instance_domain = Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
+ if (actor_info.host == instance_domain) do
+ Logger.warn("DELETE from this instance, not rejecting: #{inspect(object)}")
+ {:ok, object}
+ else
+ Logger.warn("DELETE rejected: #{inspect(object)}")
+ {:reject, object}
+ end
+ end
+
+ @impl true
+ def filter(object), do: {:ok, object}
+
+ @impl true
+ def describe, do: {:ok, %{}}
+end