JavaScript Mapping Library
A couple of days ago Fedi.Tips, an account that shares Mastodon tips, asked about how non-technical users could make use of their Mastodon archive. Mastodon makes this fairly easy (see this guide for more information), and spurred by that, I actually started work on a simple(ish) client-side application to support that. (You can see it here: https://tootviewer.netlify.app) This post isn’t about that, but rather, a look at how you can turn your archive into a web site using Eleventy. This is rather rough and ugly, but I figure it may help others. Here’s what I built.
To begin, I just created a folder and npm installed Eleventy. I’m using the latest 2.0.1 build as I’m not quite ready to go to the 3.X alpha.
npm
I shared the guide above, but to start, you’ll need to request and download your archive. This will be a zip file that contains various JSON files as well as your uploaded media.
My thinking is that I wanted to make it as easy as possible to use and update your Eleventy version of the archive, so with that in mind, I created a folder named _data/mastodon/archive. The parent folder, _data/mastodon, will include custom scripts, but inside archive, you can simply dump the output of the zip.
_data/mastodon/archive
_data/mastodon
archive
Technically, as soon as I copied crap inside _data, it was available to Eleventy. That’s awesome and one of the many reasons I love Eleventy. While the data from the archive is "workable", I figured it may make sense to do a bit of manipulation of the data to make things a bit more practical.
_data
To be clear, everything that follows is my opinion and could probably be done better, but here’s what I did.
First, I made a file named _data/mastodon/profile.js which serves the purpose of exposing your Mastodon profile info to your templates. Here’s the entire script:
_data/mastodon/profile.js
// I do nothing except rename actorlet data = require('./archive/actor.json');module.exports = () => { return data;}
So, I started this file with the intent of removing stuff from the original JSON that I didn’t think was useful and possibly renaming things here and there and… I just stopped. While there are a few things I think could be renamed, in general, it’s ok as is. I kept this file with the idea that it provides a ‘proxy’ to the archived file and in the future, it could be improved.
For your toots, the Mastodon archive stores this in outbox.json file. I added _data/mastodon/toots.js:
outbox.json
_data/mastodon/toots.js
let data = require('./archive/outbox.json');module.exports = () => { return data.orderedItems.filter(m => m.type === 'Create').reverse();}
This is slightly more complex as it does two things – filtered to the Create type, which is your actual toots, and then sorts then newest first. (That made sense to me.) Again, there’s probably an argument here for renaming/reformatting the data, but I kept it as is for now.
Create
With this in place, I could then use the data in a Liquid page like so:
<h2>Mastodon Profile</h2>{{ mastodon.profile.name }} ({{ mastodon.profile.preferredUsername }})<br>{{ mastodon.profile.summary }}<h2>Properties</h2><p><b>Joined:</b> {{ mastodon.profile.published | dtFormat }}</p>{% for attachment in mastodon.profile.attachment %}<p> <b>{{ attachment.name }}: </b> {{ attachment.value }}</p>{% endfor %}
Right away you can see one small oddity which I could see being corrected in profile.js, your join date is recorded as a published property. I really struggled with renaming this but then got over it. Again, feel free to do this in your version! That dtFormat filter is a simple Intl wrapper in my .eleventy.js config file.
profile.js
published
dtFormat
Intl
.eleventy.js
Ditto for attachment which are the ‘extra’ bits that get displayed in your Mastodon profile. You can see them here:
attachment
With no CSS in play, here’s my profile rendering on my Eleventy site:
That’s the profile, how about your toots?
I just love the word "toot", how about you? I currently have nearly two thousand of them, so for this, I decided on pagination. My toots.liquid file began with:
toots.liquid
---pagination: data: mastodon.toots size: 50 alias: toots---
That page size is a bit arbitrary and honestly, feels like a lot on one page, but it was a good starting point. My initial version focused on rendering the date and content of the toot:
<style>div.toot { border-style: solid; border-width: thin; padding: 10px; margin-bottom: 10px;}</style><h2>Toots</h2>{% for toot in toots %}<div class="toot"><p> Published: {{ toot.published | dtFormat }}</p><p>{{ toot.object.content }}</p><p><a href="{{ toot.object.url }}" target="_new">Link</a></p></div>{% endfor %}
At the end of the page, I added pagination:
<hr><p>Page: {%- for pageEntry in pagination.pages %}<a href="{{ pagination.hrefs[ forloop.index0 ] }}"{% if page.url == pagination.hrefs[ forloop.index0 ] %} aria-current="page"{% endif %}>{{ forloop.index }}</a></li>{%- endfor %}</p>
While not terribly pretty, here’s how it looks:
Not shown is the list of pages, which at 50 a pop ended up at thirty-seven unique pages. I don’t think anyone is going to paginate through that, but there ya go.
One thing missing from the toot display was embedded attachments, specifically images. In the zip file, these attachments are stored in a folder named media_attachments with multiple levels of numerically named subdirectories. A toot may refer to it in JSON like so:
media_attachments
"attachment": [ { "type": "Document", "mediaType": "image/png", "url": "/media_attachments/files/112/689/247/193/996/228/original/38d560658c00a4e8.png", "name": "A picture of kittens dressed as lawyers. ", "blurhash": "ULFY0?s,D%~W~p%Js+^+xpt6tR%LRQaeoes.", "focalPoint": [ 0.0, 0.0 ], "width": 2000, "height": 2000 }],
Not every attachment is an image, but I turned to Eleventy’s Image plugin for help. It handles everything possible when it comes to working with images. Using a modified version of the example in the docs, I built a new shortcode named mastodon_attachment to support this:
mastodon_attachment
eleventyConfig.addShortcode('mastodon_attachment', async function (src, alt, sizes) { /* todo, support other formats */ let IMG_FORMATS = ['jpg','gif','png','jpeg']; let format = src.split('.').pop(); if(IMG_FORMATS.includes(format)) { // check for valid image let mSrc = './_data/mastodon/archive' + src; let metadata = await Image(mSrc, { widths: [500], formats: ['jpeg'], }); let imageAttributes = { alt, sizes, loading: 'lazy', decoding: 'async', }; // You bet we throw an error on a missing alt (alt="" works okay) return Image.generateHTML(metadata, imageAttributes); } // do nothing console.log('mastodon_attachment sc - unsupported ext', format); return '';});
Breaking it down, it looks at the src attribute and if it’s an image, uses the Image plugin to create a resized version as well as return an HTML string I can drop right into my template. I went back to my toots.liquid template and added support like so:
src
{% if toot.object.attachment %} {% for attachment in toot.object.attachment %} {% mastodon_attachment attachment.url, attachment.name %} {% endfor %}{% endif %}
The name value of the attachment ends up being the alt for the image, and currently, I just ignore non-images, but you could certainly do something else, like link to it perhaps for downloading at least. Here’s an example of it in use:
name
alt
Ok, this was all done in about an hour or so, and as I think I said, it’s ugly as sin, but in theory, if you make it prettier then you’re good to go. You can deploy, wait a few months and get a new archive, unzip, and deploy again. Feel free to take this code and run – you can’t make it any uglier. 😉
https://github.com/cfjedimaster/eleventy-demos/tree/master/masto_archive
Raymond Camden
You must be logged in to post a comment.
This site uses Akismet to reduce spam. Learn how your comment data is processed.