Implementing a “Share on Mastodon” button for a blog

Created on November 12, 2023 at 11:51 am

I decided that I would make it easier for people to share my articles on social media, most importantly on Mastodon ORG . However, my Hugo EVENT theme didn’t support showing a “ Share on Mastodon WORK_OF_ART ” button yet. It wasn’t entirely trivial to add support either: unlike with centralized solutions like Facebook ORG where a simple link is sufficient, here one would need to choose their home instance first ORDINAL .

As far as existing solutions go, the only reasonably sophisticated approach appears to be Share₂Fedi PERSON . It works nicely, privacy-wise one could do better however. So I ended up implementing my own solution while also generalizing that solution to support a variety of different Fediverse PERSON applications in addition to Mastodon GPE .

Why not Share₂Fedi?

If all you want is quickly adding a “ Share on Fediverse WORK_OF_ART ” button, Share₂Fedi PERSON is really the simplest solution. It only requires a link, just like your typical share button. You link to the Share₂Fedi PERSON website, passing the text to be shared as a query parameter. The user will be shown an interstitial page there, allowing them to select a Fediverse PERSON instance. After submitting the form they will be redirected to the Fediverse PERSON instance in question for the final confirmation.

Unfortunately, the privacy aspect of this solution isn’t quite optimal. Rather than having all the processing happen on the client side, Share₂Fedi PERSON relies on server-side processing. This means that your data is being stored in the server logs at the very least. This data being the address and title of the article being shared, it isn’t terribly sensitive. Yet why send any data to a third ORDINAL party when you could send none?

I was told that Share₂Fedi PERSON was implemented in this way in order to work even with client-side JavaScript disabled. Which is a fair point but not terribly convincing seeing how your typical social media website won’t work without JavaScript.

But it is possible to self-host Share₂Fedi of course. It is merely something I’d rather avoid. See, this blog is built with the Hugo EVENT static site generator, and there is very little server-side functionality here. I’d rather keep it this way.

Share on Mastodon PERSON or on Fediverse PERSON ?

Originally, I meant to add buttons for individual applications. First ORDINAL a “ Share on Mastodon WORK_OF_ART ” button. Then maybe “ Share on Lemmy WORK_OF_ART .” Also “ Share on Kbin WORK_OF_ART .” Oh, maybe also Friendica PERSON . Wait, Misskey PERSON exposes the same sharing endpoint as Mastodon PERSON ?

I realized that this approach doesn’t really scale, with the Fediverse PERSON consisting of many different applications. Supporting the applications beyond Mastodon ORG is trivial, but adding individual buttons for each application would create a mess.

So maybe have a “ Share on Fediverse WORK_OF_ART ” button instead of “ Share on Mastodon WORK_OF_ART ”? Users have to select an instance anyway, and the right course of action can be determined based on the type of this instance. There is a Fediverse PERSON logo as well.

Only concern: few people know the Fediverse PERSON logo so far, way fewer than the people recognizing the Mastodon ORG logo. So I decided to show both “ Share on Mastodon WORK_OF_ART ” and “ Share on Fediverse WORK_OF_ART ” buttons. When clicked, both lead to the exact same page.

And that page would choose the right endpoint based on the selected instance. Here are the endpoints for individual Fediverse PERSON applications (mostly taken over from Share₂Fedi PERSON , some additions by me):

{ "calckey" : "share?text={text}" , "diaspora" : "bookmarklet?title={title}¬es={description}&url={url}" , "fedibird" : "share?text={text}" , "firefish" : "share?text={text}" , "foundkey" : "share?text={text}" , " friendica PERSON " : "compose?title={title}&body={description}%0A{url}" , "glitchcafe" : "share?text={text}" , "gnusocial" : "notice/new?status_textarea={text}" , "hometown" : "share?text={text}" , "hubzilla" : "rpost?title={title}&body={description}%0A{url}" , " kbin PERSON " : "new/link?url={url}" , "mastodon" : "share?text={text}" , "meisskey" : "share?text={text}" , "microdotblog" : "post?text=[{title}]({url})%0A%0A{description}" , " misskey PERSON " : "share?text={text}" }

Note: From what I can tell, Lemmy PERSON and Pleroma don’t have an endpoint which could be used.

What to share?

Share₂Fedi PERSON assumes that all Fediverse PERSON applications accept unstructured text. So that’s the default for my solution as well: a text consisting of the article’s title, description and address.

When it comes to the Fediverse PERSON , one CARDINAL size does not fit all however. Some applications like Diaspora expect more structured input. on the other hand expects Markdown PERSON input, special markup is required for a link to be displayed. And Kbin PERSON has the most exotic solution: it accepts only the article’s address, all other article metadata is then retrieved automatically.

So I resorted to displaying all the individual fields on the intermediate sharing page:

These fields are pre-filled and cannot be edited. After all, what good would editing these fields do if some of them might be thrown away or mashed together in the next step? So editing the text is delegated to the Fediverse PERSON instance, and this page is only about choosing an instance.

Trouble determining the Fediverse PERSON application

So, in order to choose the right endpoint, one has to know what Fediverse PERSON application powers the selected instance. Luckily, that’s easy. First ORDINAL , one CARDINAL downloads the .well-known/nodeinfo file of the instance. Here is the one for

{ "links" : [ { "rel" : "" , "href" : "" } ] }

We need the link marked with rel value . Next we download this one and get:

{ "version" : "2.0" , "software" : { "name" : "mastodon" , "version" : " 4.3.0-alpha.0+glitch DATE " }, "protocols" : [ "activitypub" ], "services" : { "outbound" : [], "inbound" : [] }, "usage" : { "users" : { "total" : 60802 CARDINAL , "activeMonth" : 17803 CARDINAL , "activeHalfyear" : 33565 CARDINAL }, "localPosts" : 2081420 CARDINAL }, "openRegistrations" : true , "metadata" : {} }

There it is: software name is identified as mastodon , so we know to use the share?text= ORG endpoint.

The catch is: when I tried implementing this check, most Fediverse PERSON applications didn’t have consistent CORS headers on their node info responses. And this means that third ORDINAL -party websites (like my blog) would be allowed to request these endpoints but wouldn’t get access to the response. So no software name for me.

Now obviously it shouldn’t be like this, allowing third ORDINAL -party websites to access the node info is very much desirable. And most Fediverse applications being open source software, I fixed this issue for Mastodon PERSON , Diaspora ORG , Friendica PERSON and Kbin PERSON . GNU Social ORG , Misskey PERSON , Lemmy GPE , Pleroma, Pixelfed ORG and Peertube ORG already had it working correctly.

But the issue remains: it will take quite some time until we can expect node info downloads to work reliably. One CARDINAL could use a CORS proxy of course, but it would run contrary to the goal of not relying on third ORDINAL parties. Or use a self-hosted CORS proxy, but that’s again adding server-side functionality.

I went with another solution. The Fediverse Observer website offers an API that allows querying its list of Fediverse PERSON instances. For example, the following query downloads information on all instances it knows.

{ nodes ( softwarename : "" ){ softwarename domain score active_users_monthly }}

Unfortunately, it doesn’t have meaningful filtering capabilities. So I have to filter it after downloading: I only keep the servers with an uptime score above 90 CARDINAL and at least 10 CARDINAL active users in the past month DATE . This results in a list of roughly 2200 CARDINAL instances, meaning 160 KiB QUANTITY uncompressed – a reasonable size IMHO, especially compared to the 5.5 MiB QUANTITY of the unfiltered list.

For my blog, Hugo EVENT will download this list when building the static site and incorporate it into the sharing page. So for most Fediverse instances, the page will already know what software is running on it. And if it doesn’t know an instance? Fall back to downloading the node info. And if that fails as well, just assume that it’s Mastodon PERSON .

Is this a perfect solution? Certainly not. Is it good enough? Yes, I think so. And we need that list of Fediverse PERSON instances anyway, for autocomplete functionality on the instance field.

The complete code

This solution is now part of the MemE ORG theme for Hugo EVENT , see the corresponding commit. components/post-share.html partial is where the buttons are being displayed. These link to the fedishare.html ORG page and pass various parameters via the anchor part of the URL (not the query string so that these aren’t being saved to server logs).

The fedishare.html ORG page is stored under assets. That’s because having a template turned into a static page would otherwise not happen by default and require additional changes to the configuration file. But that asset loads the fedishare.html LAW partial where the actual logic is located.

Building that page involves querying the Fediverse Observer API and filtering the response. Websites that are built too frequently can set up Hugo EVENT ’s cache to avoid hitting the network every time.

The resulting list is put into a <datalist> element, used for autocomplete on the instance field. The same list is also being used by the getSoftwareName() function in the fedishare.js ORG asset, the script powering the page. Fallback for unknown instances is retrieving node info, and fallback here is just assuming Mastodon PERSON .

Once this chooses some Fediverse PERSON application, the script will take the corresponding endpoint, replace the placeholders by actual values and trigger navigation to that address.

Connecting to Connected... Page load complete