Introducing Mastodon and Audio
My next #notai generated post is about another day of developing the kiesel app. After being atproto only for some dev days, we have approached the classic fediverse with Mastodon (ActivityPub) by implementing an adapter pattern. And as a quickwin added even the audio feed. Mastodon / ActivityPub The integration of atproto for Bluesky was roughly 12 direct @atproto/api calls to fill the feed, 8 for the profile screen and the thread screen had also 6. But now comes Mastodon. Of course we didn't start with protocol === "atproto" but introduced a new ProtocolAdapter. export interface ProtocolAdapter { readonly protocol: ProtocolType;
getTimeline(cursor?: string, limit?: number): Promise; getProfile(handle: string): Promise; getProfileFeed( handle: string, cursor?: string, limit?: number ): Promise; getThread(uri: string): Promise; // ... } the adapter gets implemented via MastodonAdapter and of course there is an additional Mastodon auth (directly with OAuth!). The Mastodon Post does not exist on your server And then ... the first bug came. Clicking a Thread ended up in a 404 error. The Thread did not exist. The thread in ActivityPub has two ids (a number and an URI). There is a number which you need for the /statuses/:id/context endpoint. But if you have just an ActivityPub-URI e.g. https://example.org/@example/12345 it will not work in this endpoint. You need to resolve it first. And you can use the search endpoint for it: GET /api/v2/search?q=&resolve=true and will make the instance federate fetch the content and give you a localId back. Afterwards you can call /statuses/:localId/context and will get your contents. This ended up in a specific function for this: private async resolveStatusId(uri: string): Promise { // If it's already a plain ID (no slashes), use it directly if (!uri.includes("/")) return uri;
// It's an ActivityPub URI from a remote instance โ resolve via search
increment("mastodonSearchResolves");
const searchResponse = await fetch(
`${this.instanceUrl}/api/v2/search?q=${encodeURIComponent(uri)}&type=statuses&resolve=true&limit=1`,
{
headers: { Authorization: `Bearer ${this.accessToken}` },
}
);
if (!searchResponse.ok) {
throw new Error(`Search failed: ${searchResponse.status}`);
}
const searchData = await searchResponse.json();
const statuses = searchData.statuses as MastodonStatus[];
if (statuses.length === 0) {
throw new Error("Status not found on your instance");
}
return statuses[0].id;
} and getThread now works with both: local IDs and ActivityPub URIs in the MastodonAdapter - and the rest of kiesel's code doesn't even know it had to. Btw. here you can see an increment("mastodonSearchResolves"), which increments a number in the local storage to remember how many api calls happened, since I am cautious to call the apis too often. Audio Tab for free In the previous post I mentioned that there is a video feed with all the videos in your feed. While using it I noticed there are also audio posts (e.g. embedded podcasts from Apple Podcasts or other sources) and was wondering if we can add audio in similar way. All posts get filtered by a new isAudioPost method to get a list of all audios: export function isAudioPost(post: UnifiedPost): boolean { const embed = post.embed; if (!embed) return false;
if (embed.type === "audio") return true;
if (embed.type === "external") { const provider = getProviderForUrl(embed.uri); if (provider && provider.mediaType === "audio") return true; }
return false; } and the provider registry got a new update with some of the common audio sources like Apple Podcasts: { id: "apple-podcasts", name: "Apple Podcasts", domains: ["podcasts.apple.com"], oembedEndpoint: "https://podcasts.apple.com/api/oembed", playerType: "webview", mediaType: "audio", autoplayAllowed: false, autoplayDefault: false, requiresMiddleware: false, profileAccess: "direct", consentRequired: true, }, The embedding is a webview again and it has an oEmbed endpoint. What a breeze! See you in the next dev day post!
Discussion in the ATmosphere