This content originally appeared on Raymond Camden and was authored by Raymond Camden
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.
Start with a Fresh Eleventy Site #
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.
Store the Archive #
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.
Expose the Data #
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.
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:
// 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
:
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.
Rendering the Profile #
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.
Ditto for attachment
which are the 'extra' bits that get displayed in your Mastodon profile. You can see them here:
With no CSS in play, here's my profile rendering on my Eleventy site:
That's the profile, how about your toots?
Rendering the 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:
---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.
Supporting Images #
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:
"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:
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:
{% 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:
Show Me the Code! #
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
This content originally appeared on Raymond Camden and was authored by Raymond Camden
Raymond Camden | Sciencx (2024-07-04T18:00:00+00:00) Building a Web Version of Your Mastodon Archive with Eleventy. Retrieved from https://www.scien.cx/2024/07/04/building-a-web-version-of-your-mastodon-archive-with-eleventy/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.