🇵🇸 Donate eSIMs to Gaza 🇵🇸

POSSE

A directed graph diagram, with a circle titled
Image from https://indieweb.org/POSSE

I first saw POSSE (Publish (on your) Own Site, Syndicate Elsewhere) in action on Cory Doctorow’s https://pluralistic.net/ blog.

Everytime Doctorow writes a new blog post, he publishes it to not only his own site, but also to his Twitter and Mastodon feeds as a series of linked tweets/posts.

The idea is that you control your own data, but can still reach people that only use, say, Twitter. They don’t even to need to know your website exists.

And then, if you delete your Twitter account, you don’t lose all your data – your posts are still saved on your website and on any other service you syndicated them to.

I don’t POSSE my blog posts personally, since I think they’re a bit too long for easy reading on a Twitter style platform, and have some interactivity that would be lost in a text only medium.

But as I planned to build my new /notes page, I figured it’d be the perfect chance to try POSSE!


I launched /notes a couple weeks ago: a microblog with social media style posts that are too small to make a full blog post out of, inspired by David Bushell.

They drastically lower the mental barrier to writing for me – a blog post feels effortful, a small note much less so. And anything that gets me writing more is a good thing!

I decided that I wanted to also post these notes to my Mastodon (my current social media platform of choice) both to increase my potential audience and as an excuse to mess around with the Mastodon API.

I’m proud to say that the experiement was a success! Notes get automatically sent to Mastodon when I deploy my website, with no manual effort from me!

You can see my first note here: /notes/1/ which was also published on Mastodon as jawns.club/@ragman/113782381174232073.

A screenshot of the mastodon link above. Click on that link to see it.

I’m particularly happy that with the custom link behavior.

Mastodon doesn’t support normal inline links (like this one). Instead you have to put URLs on their own line and they’ll get turned into links (this is what Doctorow does). That works fine, but I wanted the ability to do normal links on my site.

My solution was to use some custom code to turn inline links into citation style links [1] for Mastodon.

1. which_look_like_this.com

Post on my own site, syndicate a slightly different post elsewhere!


In the future I may also start posting my notes to Bluesky… if I ever get around to setting up an account!

In the section below, I’ll break down the technical details of how I did this.

My blog is completely custom, so my code probably won’t directly work for you, but maybe it could give you some ideas!

If you’re looking for an out of the box solution, there’s some available at https://indieweb.org/POSSE.


How to POSSE!

I publish my notes to Mastodon using their API. This is two step process:

  1. Call the Mastodon /media API to upload any media (e.g. images) I want to attach to my post.
  2. Call the Mastodon /statuses endpoint to actually make the post.

I then take the resulting post ID and save it in my /notes SQLite database.

That database is the source of truth that my blog generation code uses to generate my site’s HTML – if a note has a mastodon_id field, it’ll get rendered with a Discuss on Mastodon link.

Lets go over how to use the Mastodon API.

Mastodon API

The API is well documented and pretty easy to use.

It uses OAuth for authentication, so before you can start using the API to post on your behalf, you need to create the appropriate OAuth access token.

Setting up authentication

The first thing you’ll need to do is to create an “Application” – basically an entity on your account that can be granted permissions to do things on your behalf.

You can create this Application through the API following the steps here, or through the UI at https://<your-mastodon-instance>/settings/applications if your instance supports it.

I did the latter and it was quite easy!

To get it to post for you, you need to grant it the write:media and write:statuses scopes. The former is to allow image embeds, and the latter for the posts themselves.

Once you’ve done that, you should be presented with an OAuth access token. Pass that token in to any API calls (using the HTTP Authorization header) and your requests will be authorized!

Making a post

You make posts, which the API calls statuses, by making a POST request to the https://\<your-mastodon-instance\>/api/v1/statuses endpoint. You can see the full API documentation here.

The Node JS code I’m using to make the POST looks something like this:

const body = {
    "status": "note text from database here"
};

if (note.hasMedia) {
    body["media_ids"] = [note.mediaID];
}

if (notFirstChunk) {
    body["in_reply_to_id"] = previousID;
}

const idempotencyKey = crypto.randomUUID();

const opts = {
    method: "POST",
    headers: {
        // This is the token from your Application
        "Authorization": "Bearer <my token>",

        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey,
    },
    body: JSON.stringify(body)
};

const res = await fetch("https://jawns.club/api/v1/statuses", opts);

Some things to note are:

  • Authorization header

    This is where your OAuth token goes. Without this you’ll get Permission Denied errors.

  • Idempotency-Key header

    This is passed so that if the Mastodon server receives my API request twice, it doesn’t double post. Per the docs, idempotency keys are cached for one hour which is more than sufficient for me.

    If you’re not familiar with why idempotency is important, check out this Tom Scott video where he explains it really well.

  • status and in_reply_to_id

    Status is where the text content of your post goes. If it’s over 500 characters, you’ll get an error message.

    I wrote a little bit of code which chunks up my Notes into 485 character posts, which get POSTed individually.

    The in_reply_to_id field is used to link posts into a thread, which each post setting the id of the one before it.

  • media_ids

    These are the ids of any media (e.g. images, videos) you want to attach to your post. You have to create these before you call the statuses endpoint, using the /api/v2/media endpoint (see below).

If this API call returns a 200, your post should be live!

Posts with media

As I mentioned above, if your post has any media attached, you need to call make a POST to /api/v2/media first to upload the media to Mastodon. Docs are here.

This gives you an id, which you pass in the media_ids array field of the /statuses POST.

One super important thing is that this call is potentially asynchronous.

For small files (maybe all images?) Mastodon returns a 200, and provides you the id and media URL right away.

But for large files (maybe all videos?), Mastodon will return a 202: Accepted, indicating that your media is still processing.

In this case, you still get the id, and so can still pass it in your /statuses call – but if you do that before Mastodon has finished processing the media, it might not show up on your post.

You can call the /api/v1/media/:id endpoint to see if it’s finished processing.

Therefore, if you get a 202 from your initial /media POST, you should poll the /media:id endpoint until your media has been processed and then make your /statuses POST.

I’m not actually doing this myself (my code assumes the 200), because I’m only posting images. I’ll probably change that soon, and will update this section if I learn anything notable!

As I said at the beginning of the article, Mastodon doesn’t allow inline links with different text than their link address, like this one.

I reckon this is a good thing security wise, cuz otherwise social media would be full of phishing links and rick rolls.

But I stubbornly wanted my Notes section to have links just like the rest of my website, and fortunately, I found a way!

I write my blog posts and notes in Markdown and use a wonderful program called pandoc to turn that Markdown into HTML.

I did some looking around to see if pandoc had some solution to my link problem. I didn’t find anything built in, but `lo and behold, somebody on Github had written a pandoc addon that did exactly what I wanted:

https://gist.github.com/riceissa/d5015a67160ca5c0f8ded99102f84a4b

#!/usr/bin/python3

import sys

from pandocfilters import toJSONFilter, Para, Link, Note, Str, stringify, Space

def footnotify(key, value, format_, meta):
    if key == "Link":
        _, txt, (url, title_text) = value
        # Ignore bare links like <http://example.com>; otherwise we run into a
        # recursive problem where Pandoc tries to apply this same filter to the
        # new link in the footnote. Also ignore URLs that begin with "#", since
        # those point to sections within the document.
        if stringify(txt) != url and not url.startswith("#"):
            empty_attrs = ["", [], []]
            cite_link = Link(empty_attrs, [Str(url)], [url, ""])
            if title_text:
                lst = [Str(title_text), Space(), cite_link]
            else:
                # There was no title text, so we do just what links-as-notes
                # would do.
                lst = [cite_link]
            res = txt + [Note([Para(lst)])]
            return res

if __name__ == "__main__":
    toJSONFilter(footnotify)

This footnotify.py function by riceissa is a pandoc filter which takes any inline links in your input and turns them into citation style links.

It turns a post like this:

This is my sentence with a link in it.

into this:

This is my sentence with a link [1] in it.

  1. https://my-link-here.com

Mastodon will then take the plain link text and linkify it too.

So, before my POSSE script calls the Mastodon API, it runs the Note markdown content (stored in my DB) through pandoc like so:

pandoc -f markdown -F footnotify.py -t plain --wrap=preserve /path/to/markdown

The wrap=preserve flag is so that it doesn’t automatically truncate each line at 72 characters, pandoc’s default for plain text (see their docs here).

With that that footnote-ified note is ready to be posted!


That’s all for now, hope it was helpful! Feel free to email me or hit me up on Mastodon if you’ve any questions or want help POSSE-ifying your own site :)