A photo of Evan Pratten
Evan Pratten

Lazy search for Mastodon accounts

Webfinger + Cloudflare Workers + Firefox custom search engines

Anyone who has been using Mastodon (or other ActivityPub servers) for a bit might have noticed how its a little annoying to look up someone’s “home profile” from their account handle. My personal flow goes something like:

  1. Open my own mastodon instance
  2. Search the other user’s handle
  3. Find the account in the results
  4. Use the Open original page button to jump to their full profile

Luckily, with a little bit of server-side tinkering, this process can be cut down to a single step:

  1. Search the user’s handle in my browser searchbar

A bit of background

Most (notable) decentralized social media services are built on a web standard called ActivityPub. ActivityPub handles all aspects of cross-instance communication, including the process of looking up users on another instance.

This cross-instance user lookup system utilizes RFC7033: WebFinger, a modern replacement / reimplementation of the old RFC742: Name/Finger protocol.

For example, if you wanted some information about my personal @[email protected] account, you can send an HTTP GET request to the following URL:

https://ewpratten.com/.well-known/webfinger?resource=acct:[email protected]

..and you’ll get back a response that looks something like this:

{
  "subject": "acct:[email protected]",
  "aliases": [
    "https://social.ewpratten.com/@evan",
    "https://social.ewpratten.com/users/evan"
  ],
  "links": [
    {
      "rel": "http://webfinger.net/rel/profile-page",
      "type": "text/html",
      "href": "https://social.ewpratten.com/@evan"
    },
    {
      "rel": "self",
      "type": "application/activity+json",
      "href": "https://social.ewpratten.com/users/evan"
    },
    {
      "rel": "http://ostatus.org/schema/1.0/subscribe",
      "template": "https://social.ewpratten.com/authorize_interaction?uri={uri}"
    }
  ]
}

Notice the links[0].href field above? This is exactly the URL for my profile page, on my home instance!

Plan of action

Now that we know how to get a profile’s “home” URL via WebFinger, we can write a little script to convert an account handle query into a 302 redirect to that account’s URL.

The plan is to make a query like:

GET https://mastodon-redirect.example.com/profile/@[email protected]

and get a repsonse of:

HTTP/1.1 302 Found
Location: https://social.ewpratten.com/@evan

Implementing the redirect script

I chose to do this with a Cloudflare Worker, since my domains are already managed by them, and I have free credits to use.

The script to get this working is pretty simple:

export default {
  async fetch(request, env) {
    try {
      const { pathname } = new URL(request.url);

      // If the root is hit, return a basic info page
      if (pathname == "/" || pathname == "") {
        return new Response("This is a webfinger redirect service.");
      }

      // Put together common info other functions may need
      let profile = pathname.split("/profile").reverse()[0].replaceAll("%40", "@").replace("/@", "").replace("/", "");
      let domain = profile.split("@").reverse()[0];
      let webfinger_url = `https://${domain}/.well-known/webfinger?resource=acct:${profile}`;

      // Handle profile redirects
      if (pathname.startsWith("/profile")) {

        // Make a request to the webfinger url for JSON data
        let response = await fetch(webfinger_url);
        
        // If the response fails, return in
        if (!response.ok){
          return response;
        }
        let response_json = await response.json();

        // Parse the profile url out of the response
        let profile_url = null;
        for (let i = 0; i < response_json.links.length; i++){
          if (response_json.links[i]["rel"] == "http://webfinger.net/rel/profile-page"){
            profile_url = response_json.links[i]["href"];
            break
          }
        }

        // Handle the response
        if (profile_url) {
          return new Response("", {
            status: 302,
            headers: {
              "Location": profile_url
            }
          })
        } else {
          return new Response("Could not find a profile-page link for this profile", {status: 404});
        }
      }
    } catch(e) {
      return new Response(e.stack, { status: 500 })
    }
  }
}

For the rest of this post, I’ll assume you have this hosted at mastodon-redirect.example.com.

Tying everything together with a Firefox search prefix

Note: This probably also works in Chromium-based browsers. I just don’t remember the details of how

Firefox lets users set a prefix on their bookmarks. With a prefix set, you can search things with @yourprefix This is a thing, and the browser will string-substitute This is a thing into the bookmark url. Its a handy way to trigger a search inside a specific website without first opening that site.

To set up a search prefix for the redirect service, we need to do the following:

  1. Create a new bookmark
  2. Set the url to https://mastodon-redirect.example.com/profile/%s
  3. Set the prefix to @mastodon (or whatever else you want)

Finally, you can search @mastodon @[email protected] in your searchbar, and you’ll end up on my profile page!