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.
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:
- Call the Mastodon
/media
API to upload any media (e.g. images) I want to attach to my post. - 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) {
"media_ids"] = [note.mediaID];
body[
}
if (notFirstChunk) {
"in_reply_to_id"] = previousID;
body[
}
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!
Posts with links
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":
= value
_, txt, (url, title_text) # 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 = Link(empty_attrs, [Str(url)], [url, ""])
cite_link if title_text:
= [Str(title_text), Space(), cite_link]
lst else:
# There was no title text, so we do just what links-as-notes
# would do.
= [cite_link]
lst = txt + [Note([Para(lst)])]
res 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.
- 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 :)