MRF, or Message Rewrite Facility, is a way for Pleroma to trigger side-effects on ActivityPub messages, unlocking nearly infinite possibilities.

When you author an up-to-5000 character post on Pleroma, it goes through Pleroma's ActivityPub "Pipeline." Within this pipeline is a layer called MRF, which lets users decide what can happen to ActivityPub objects.

Of course, posts aren't the only type of ActivityPub object, there are also follows, blocks, deletes, and more.

With MRF, any side-effect you can imagine is possible. We could achieve (but aren’t limited to):

  • Blocking all activities from certain servers.
  • Discarding all messages with offensive slurs.
  • Discarding all messages in French.
  • Duplicating each message 100 times.
  • Automatically submitting each message to the FBI.
  • Displaying all text in reverse.
  • Replacing all text with just the letter “e”.
  • Using AI to detect pictures of anime girls and discarding them.
  • Using AI to detect pictures of anime girls and discarding everything except them.
  • Switching around everyone’s display names and avatars.
  • Making an LED blink every time an activity is processed.
  • Making a smart home flip its light off and on every time a message containing #fediblock is processed.

How do I enable an MRF policy?

At the time of writing, Pleroma ships with 21 MRF policies an admin can configure on their Pleroma server. To do so, they can be added through AdminFE.

MRF policy configuration in AdminFE

Once enabled, an MRF policy can be configured with settings specific to that policy. Each policy is configured differently.

Configuring the MRF SimplePolicy to unlist posts from a single domain

The 21 bundled MRF policies are listed below.

  • ActivityExpirationPolicy - Adds expiration to all local Create activities
  • AntiFollowbotPolicy - Prevent followbots from following with a bit of heuristic
  • AntiLinkSpamPolicy
  • DropPolicy - Drop and log everything received
  • EnsureRePrepended - Ensure a re: is prepended on replies to a post with a Subject
  • ForceBotUnlistedPolicy - Remove bot posts from federated timeline
  • HellthreadPolicy - Block messages with too much mentions (configurable)
  • KeywordPolicy - Reject or Word-Replace messages with a keyword or regex
  • MediaProxyWarmingPolicy - Preloads any attachments in the MediaProxy cache by prefetching them
  • MentionPolicy - Block messages which mention a user
  • NoOpPolicy - Does nothing (lets the messages go through unmodified)
  • NoPlaceholderTextPolicy - Ensure no content placeholder is present (such as the dot from mastodon)
  • NormalizeMarkup - Scrub configured hypertext markup
  • ObjectAgePolicy - Filter activities depending on their age
  • RejectNonPublic - Rejects non-public (followers-only, direct) activities
  • SimplePolicy - Filter activities depending on their origin instance
  • StealEmojiPolicy - Detect new emojis by their shortcode and steals them
  • SubchainPolicy - Processes messages through an alternate pipeline when a given message matches certain criteria
  • TagPolicy - Apply policies based on user tags
  • UserAllowListPolicy - Accept-list of users from specified instances
  • VocabularyPolicy - Filter messages which belong to certain activity vocabularies

How it works (in code)

So how does this work? In short, an MRF policy defines a function like def filter(object), which gets called by Pleroma's pipeline for every object that goes in or out. The simplest example is Pleroma's NoOp policy, which does nothing:

defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
  @moduledoc "Does nothing (lets the messages go through unmodified)"
  @behaviour Pleroma.Web.ActivityPub.MRF

  @impl true
  def filter(object) do
    {:ok, object}
  end

  @impl true
  def describe, do: {:ok, %{}}
end

To do more, you could add some more logic, perhaps something like this:

defmodule AnimeMRF do
  @moduledoc "Filters out messages containing anime girls."
  @behaviour Pleroma.Web.ActivityPub.MRF

  @impl true
  def filter(object) do
    if has_anime_girl(object) do
      {:reject, "Sorry, no anime girls allowed."}
    else
      {:ok, object}
    end
  end

  defp has_anime_girl(object) do
    # TODO: Some artificial intelligence on the image...
  end

  @impl true
  def describe, do: {:ok, %{}}
end

It helps to know what an Activity looks like. In ActivityPub, a post (aka "Note") looks something like this:

{
  "type": "Create",
  "id": "https://gleasonator.com/objects/1",
  "actor": "https://gleasonator.com/users/alex",
  "object": {
    "type": "Note",
    "attributedTo": "https://gleasonator.com/users/alex",
    "content": "Soapbox FE v1.1 was released today! 🎉 This comes after a lot of hard work.",
    "attachment": [
      "https://media.gleasonator.com/uploads/soapbox.jpg"
    ],
    "id": "https://gleasonator.com/objects/2",
    "published": "2020-10-05T17:25:00Z",
    "to": [
      "https://www.w3.org/ns/activitystreams#Public"
    ],
    "cc": [
      "https://gleasonator.com/users/alex/followers"
    ]
  },
  "published": "2020-10-05T17:25:00Z",
  "to": [
    "https://www.w3.org/ns/activitystreams#Public"
  ],
  "cc": [
    "https://gleasonator.com/users/alex/followers"
  ]
}

With this knowledge, it's possible to do pattern matching in Elixir to easily filter images with attachments. This helps us only check posts that have attachments, and let all else pass through.

defmodule AnimeMRF do
  @moduledoc "Filters out messages containing anime girls."
  @behaviour Pleroma.Web.ActivityPub.MRF

  @impl true
  def filter(object) do
    if has_anime_girl(object) do
      {:reject, "Sorry, no anime girls allowed."}
    else
      {:ok, object}
    end
  end

  # Only gets called if there's an attachment
  defp has_anime_girl(%{"object" => %{"attachment" => attachment}}) do
    # Call a library to do an AI check on the image
    AnimeChecker.is_anime_girl(attachment)
  end

  # If there's no attachment, there's no anime girl
  defp has_anime_girl(_), do: false

  @impl true
  def describe, do: {:ok, %{}}
end

MRFs from the community

Due to its power and flexibility, some users have made custom MRF policies not found in Pleroma itself. These modules can be added to Pleroma by copying them in and configuring them manually.

  • BlockPolicy by nik - Notify local users upon remote block.
  • NotifyLocalUsersPolicy by a1batross - Notifies all local users by configurable tag in subject field (only for admins)
  • ... a lot more that I don't have links for (send them my way?)

To find out more about MRF, follow the links below: