Managing a Mastodon domain block list

If you are hosting a Mastodon server or helping administer one, domain blocks are something you should be using. Domain blocks help administrators prevent other servers and accounts from appearing on your server.

In order to automate this, I wrote a basic Node script that can read a list of domains and then automatically update my Mastodon server via the admin API.

Let's look at what you will need to run the script.

Requirements

You will need three things to run the node script. The first is the URL of your server, which you are likely familiar with - in my case, this is https://zsiegel.social. Next, you must create an access token with the appropriate Admin API scopes.

On your Mastodon server, navigate to Preferences -> Development and click New Application. You can give it a name and then adjust the scopes to only include admin:read and admin:write. After you hit submit, you will be shown the Access Token.

Finally, you will need to create a text file that contains a list of domains you want to block. Each domain should be placed on a new line.

Updating the server

To update the Mastodon server, you need a Node environment v17 or above. I always recommend running on the latest version available if possible. You then need to set the information you collected in the requirements section as environment variables.

  • MASTODON_BLOCKLIST Contains the file path to your blocklist
  • MASTODON_ACCESS_TOKEN Set this to the access token you got from your server
  • MASTODON_URL Finally set the URL of your Mastodon server

The script has no dependencies, so you can run node update.js to run the script. If you name the script something other than update.js simply replace that in your command.

You can grab the contents of the script below.

const { readFile } = require("fs/promises");

(async function main() {
  let MASTODON_URL = process.env.MASTODON_URL;
  let MASTODON_ACCESS_TOKEN = process.env.MASTODON_ACCESS_TOKEN;
  let MASTODON_BLOCKLIST = process.env.MASTODON_BLOCKLIST;

  console.log(`Reading blocklist: ${MASTODON_BLOCKLIST}`);

  let blocklist = await (await readFile(MASTODON_BLOCKLIST, "utf8")).split(
    "\n"
  );

  console.log(`Found ${blocklist.length} domain(s) to block`);

  let currentDomains = await getCurrentDomains(
    MASTODON_URL,
    MASTODON_ACCESS_TOKEN
  );
  console.log(`Server has ${currentDomains.length} domain(s) defined`);

  //Convert the array to a map of { domain : item }
  let domainMapping = new Map(
    currentDomains.map((item) => [item.domain, item])
  );

  //Remove any domains from the server that are not in the blocklist
  currentDomains.forEach(async (item) => {
    if (!blocklist.includes(item.domain)) {
      console.log(`Domain ${item.domain} should be removed`);
      let response = await deleteDomain(
        MASTODON_URL,
        MASTODON_ACCESS_TOKEN,
        item.id
      );
      if (response.ok) {
        console.log(`--- Domain ${item.domain} deleted`);
      } else {
        console.log("--- Unable to delete domain");
      }
    }
  });

  //Add any domains to the server that exist in the blocklist
  blocklist.forEach(async (domain) => {
    if (!domainMapping.has(domain)) {
      console.log(`Adding domain ${domain}`);
      let response = await addDomain(
        MASTODON_URL,
        MASTODON_ACCESS_TOKEN,
        domain
      );
      if (response.ok) {
        console.log(`--- Domain ${domain} added.`);
      } else {
        console.log(
          `--- Error adding the domain ${domain}. Status: ${response.status}`
        );
      }
    }
  });
})();

const addDomain = async (server, authToken, domain) => {
  let url = new URL("/api/v1/admin/domain_blocks", server);
  url.searchParams.set("domain", domain);
  url.searchParams.set("severity", "suspend");
  url.searchParams.set("reject_media", true);
  url.searchParams.set("reject_reports", true);
  url.searchParams.set("obfuscate", true);

  const response = await fetch(url, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${authToken}`,
    },
  });

  return response;
};

const deleteDomain = async (server, authToken, id) => {
  const response = await fetch(`${server}/api/v1/admin/domain_blocks/${id}`, {
    method: "DELETE",
    headers: {
      Authorization: `Bearer ${authToken}`,
    },
  });

  return response;
};

const getCurrentDomains = async (server, authToken) => {
  const response = await fetch(
    `${server}/api/v1/admin/domain_blocks?limit=500`,
    {
      headers: {
        Authorization: `Bearer ${authToken}`,
      },
    }
  );
  if (!response.ok) {
    console.log(
      `Unable to get domains from server. status: ${response.status}`
    );
  }
  return response.json();
};