166 lines
5.8 KiB
Markdown
166 lines
5.8 KiB
Markdown
---
|
|
title: Lazy search for Mastodon accounts
|
|
description: Webfinger + Cloudflare Workers + Firefox custom search engines
|
|
date: 2023-01-17
|
|
tags:
|
|
- project
|
|
- javascript
|
|
draft: false
|
|
extra:
|
|
auto_center_images: true
|
|
excerpt: I made a custom search prefix for my browser that can resolve ActivityPub
|
|
accounts into their profile pages
|
|
discuss:
|
|
reddit: https://www.reddit.com/r/ewpratten/comments/12xzulo/lazy_search_for_mastodon_accounts/
|
|
aliases:
|
|
- /blog/webfinger-redir
|
|
---
|
|
|
|
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](https://www.w3.org/TR/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](https://www.rfc-editor.org/rfc/rfc7033), a modern replacement / reimplementation of the old [RFC742: Name/Finger](https://www.rfc-editor.org/rfc/rfc742) protocol.
|
|
|
|
For example, if you wanted some information about my personal `@evan@ewpratten.com` account, you can send an HTTP GET request to the following URL:
|
|
|
|
```text
|
|
https://ewpratten.com/.well-known/webfinger?resource=acct:evan@ewpratten.com
|
|
```
|
|
|
|
..and you'll get back a response that looks something like this:
|
|
|
|
```json
|
|
{
|
|
"subject": "acct:evan@ewpratten.com",
|
|
"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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302) to that account's URL.
|
|
|
|
The plan is to make a query like:
|
|
|
|
```text
|
|
GET https://mastodon-redirect.example.com/profile/@evan@ewpratten.com
|
|
```
|
|
|
|
and get a repsonse of:
|
|
|
|
```text
|
|
HTTP/1.1 302 Found
|
|
Location: https://social.ewpratten.com/@evan
|
|
```
|
|
|
|
## Implementing the redirect script
|
|
|
|
I chose to do this with a [Cloudflare Worker](https://workers.cloudflare.com/), since my domains are already managed by them, and I have free credits to use.
|
|
|
|
The script to get this working is pretty simple:
|
|
|
|
```js
|
|
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 @evan@ewpratten.com` in your searchbar, and you'll end up on my profile page!
|