Log in


Day 25: 'better' RSS feed

A blast from the past: I have an RSS feed and it's available at seblog.nl/feed.rss. (I also have seblog.nl/feed, which is actually the h-feed version of it.) I made the RSS feed a while ago for Martijn, who I believe is my only subscriber at the moment.

Back when I made the feed, I didn't want to put in much work for it, because I already have a couple of rich h-feeds. So I made it so that "if a post has a title, give that, else, just call it 'post'" and "if a post has content, give that, else, just say 'A post on Seblog'". Very lazy. It turns out I actually have a lot of posts that have neither a title nor content. Things like my likes, reads and bookmarks all have no title nor content, because it is stored in different fields. So my RSS feed ends up being filled with contentless posts.

I am still lazy about my RSS feed, but I improved it a bit. I now just call the same snippet that produces the HTML for the frontpage for the content of the feed item. This means the whole post is in the RSS feed. Probably not the best way to do it, but hey, who uses RSS nowadays anyway?

@martijn, let me know how it looks!

Day 24: media-endpoint at HWC

Today I was at Homebrew Website Club in Utrecht. We had some talks, showed some stuff, and then coded for a while. (You might say that we switched the quiet writing hour and the talks around, but we were not silent all the time.) Jesse made a first version of a blog section on his site and Martijn did some tweaking on his habit tracker.

I made it easy for myself: I just copy+pasted my existing [media endpoint](https://www.w3.org/TR/micropub/#media-endpoint) into my Indieweb Toolkit. Then I spent the rest of the time tweaking, cleaning and improving my code. The whole endpoint is now contained in a single static method, which takes a string with the path to the upload folder, and a string with the public URL of that same folder. It validates access tokens, saves media with a sanitised filename and an unguessable prefix, like vx5dgk-image.jpg, and then returns the URL of the uploaded file in a Location header.

So, this could be your media-endpoint.php:

require __DIR__ . DIRECTORY_SEPARATOR . 'toolkit' . DIRECTORY_SEPARATOR . 'bootstrap.php';
require __DIR__ . DS . 'indieweb-toolkit' . DS . 'bootstrap.php';

endpoint::media(__DIR__ . DS . 'media', 'http://indieweb-toolkit.dev/media');

Still haven't brought the IndieAuth class over to the toolkit though, so it's still not working. But it's coming!

Day 23: automatic retweet

Okay, I admit: I already did this yesterday, as you can see from my previous retweet. But I wanted one day off. One day without screens. So that’s today.

Earlier on I made my site like tweets when I post a like to a tweet on my site. This means I get just feed my Micropub endpoint an URL as a like-of, and don’t think about it anymore. Now I added reposts to this too, so I can just paste a link in Quill and it’s ok.

I also seemed to miss pictures in reposts since I redid them, so I fixed that as well. It’s the little things. See you tomorrow!

Day 22: read posts

In December I already posted some plain text notes tracking my reading progress in Stoner by John Williams. (The Dutch translation, by the way, because I got that one somewhere in a very portable version and I was on a city trip to Vilnius.) I already planned to upgrade those plain text notes to a more standarized format, so that I would have /read posts.

Before I could add them, though, I needed a way to give them URLs. So now I have this public list of books I own / owned / want to track, on my new library page: /bieb. All books in there are marked up as h-cites and have a url in the format https://seblog.nl/isbn/9780123456789. Their u-uid is set to isbn:9780123456789, because that seems to be a valid url scheme (but browsers don't know what to do with it).

The next step was simple: I just expanded my shortpost type with a 'read' type, that looks for a read-of field. Thanks to XRay and my existing code, the seblog.nl/isbn/-url gets expanded to a title and author. I also added a page field, because I posted when I was at certain points at the book, and I actually haven't finished it yet.

Now my new /gelezen page looks like this:

Day 21: basic Micropub endpoint

Today I added a very basic Micropub endpoint to my Indieweb Toolkit. A Micropub endpoint is a hard thing to keep generic, because every site has a different way of storing data. The idea about the endpoint in the toolkit is that it does all the things that are the same about Micropub.

So here's an example of what it can do. This one simply writes a YAML file with the received fields to a folder. Please note that it overwrites any post with an existing slug. It's only an example.

  'create' => function($type, $fields, $slug) {
    $map = [
      'entry' => 'blog',
      'event' => 'events',
      'review' => 'reviews',

    if(!isset($map[$type])) return false;
    if(!$slug) $slug = uniqid();

    yaml::write(__DIR__ . DS . $map[$type] . DS . $slug . '.yml', $fields);

    return url::makeAbsolute('/'.$map[$type] . '/' . $slug . '.yml');

The idea is that you can put this code where-ever you want. It can be at /micropub.php or in some route you define. The new endpoint::micropub() does all the authentication for you and returns the appropriate header. All you need to do is pass in a 'create' callback function, which takes the fields and returns a URL. endpoint::micropub() will do the redirect with a 201 header.

At this moment it only supports 'create' with x-www-form-urlencoded, but JSON and 'delete', 'undelete' and 'update' will follow. I also want callbacks for different mp-synticate-tos, and of course there is the ?q=config query. But I like this idea, and will use this idea to clean out the code of my own endpoint.

Last caveat: at this moment I have not implemented the IndieAuth class in my toolkit. You can steal the class from my kirby-micropub plugin, but I want to clean it first before putting it in the toolkit.

Day 20: automatic liking on Twitter

Today I drafted a SiloAPI class in indieweb-toolkit. The idea is that it gives an easy way to interact with different Silo API's. In some ways this is a bad idea, because all API's work different and have different capabilities, but I like the idea, even if it's bad.

One API call that is available for both Twitter, Instagram and Facebook is to create a like. So that's where I focussed on first. Today, I made SiloAPI a wrapper for the TwitterOAuth PHP library, and it now supports ::like() just like micropub::like(). You still need to pass in the name of the silo though, but maybe I'll detecting the silo from the URL later.

siloapi::setToken('twitter', [
  'consumer_key'       => 'xxx',
  'consumer_secret'    => 'xxx',
  'oauth_token'        => 'xxx',
  'oauth_token_secret' => 'xxx',

siloapi::like('https://twitter.com/_/status/123456', 'twitter');

Then I realised that Brid.gy probably can do this for me too, since it already syndicates my Tweets for me when I ask for it. So I just made my Micropub endpoint call Brid.gy publish for every like-of that is a twitter-URL.

I will add to SiloAPI though, because I want to autolike to Instagram too!

Day 19: reposts again

Today is a weird day. Fixed a lot of things, but also did kinda useless things. (I learned more about HTTP requests by trying to create a Micropub request using netcat, and at some point, I was chatting with myself in two Terminal windows with nc -l localhost 8080 in one and nc localhost 8080 in the other, completely by accident.)

Then it was 1:30 at night.

Earlier on in this series, I fixed my repost context. These contexts where based on a .json file I exported with my whole Twitter archive. A few days ago I made XRay look up likes for me. Today, I retweeted someone, and since .json files don't magically appear on your server (luckily), it didn't show any repost context.

So I combined the two and now XRay fetches the repost context for me. I don't use the .json file anymore, but keep the data the same way I keep likes: in a refs field.

And then it was 2:00 at night. I'm going to bed.

Day 18: Micropub helper class

Today I extended my new Indieweb Toolkit with a Micropub helper class. This allows you to easily send Micropub requests from your PHP code. Here’s an example:

// Set URL and access token of the endpoint to use
micropub::setEndpoint('http://yoursite.com/micropub', 'xxx');

micropub::reply('http://example.com/a-nice-post', "Oh what a post!");
micropub::rsvp('http://example.com/an-event', 'maybe');

$newURL = micropub::post([
  'name' => 'Custom posts are possible!',
  'content' => 'This is a story about (...)',
  'category' => ['story', 'custom'],
  'mp-slug' => 'custom-posts-are-possible',
  'mp-syndicate-to' => 'https://twitter.com/example',


Note that this only creates posts, and relies havily on the server you are making requests to. The mp-syndicate-to in the above example indicates that the remote server needs to post the entry to Twitter also, this helper doesn’t do a thing for that.

A drop-in Micropub endpoint is hard, because every site stores it’s data different, but I intend to extend this toolkit so that the different parts involved will be easier. (De facto making my own code more re-usable.)

Posted this post with use of todays code :D

Day 17: okay, what now?

It’s only Day 17, and while there is allways stuff to do, I feel like I got to a point where everything more or less works like I think it should. To put it in another way: most of my itches have been rubbed.

At the same time, I feel like I have implemented a lot of stuff others have already build in some other form. Apart from my questionable new post type, I have not really build new things. And that’s no problem: nothing is entirely new, all things build on top of other things. My problem with it is that I keep building things other people build before me because they build it. I need to step back and think again: do I need this?

Another problem with the challenge is that I did a lot of ‘small’ things already, but keep seeing only big things on my list. This is both a problem (I keep postponing the big things) and the solution (this challenge forces me to break things down!).

There are a few general areas where I want to make progress that are just a bit to big:

  • Webmentions – I have them working, but I made some changes to the original Kirby plugin that might not be for everyone. I kind of want to start a new basic plugin, providing just sending and receiving Webmentions, possibly including a Panel Widget too (although I don’t use the panel myself). And then offer an extension to that plugin with Microformats parsing and display of comments.

  • Micropub – I wrote a Micropub plugin for Kirby, and it’s only the best out there because it’s the only one out there. I branched off the main branch to do some guild free drafting of the [update](https://www.w3.org/TR/micropub/#h-update) function. My own site uses the latest commit of that branch, the code is horrible and I have never merged it. The problem here is that there is quite a bit of technical debt in that plugin. It needs fixing anyway, although it does work for my site.

  • Importing old data – I’m just postponing on this one because it’s not very spectacular. But I think it’s okay to import some stuff every now and then and call it a day. I still have Strava, Hyves, my old Facebook, old blogposts from before april 2009 and three Vines. The problem here is that my current data is not really clean. I have tweets and blogposts that could be deduped, and the utf-8 conversion for some posts seems to be weird. Cleaning data doesn’t sound like a good thing within this challenge, so I’m postponing importing all together.

  • Private posts – This one might not be as big as the others in terms of work, but is does come in steps. I want to use IndieAuth, but also give Silo-friends a way of seeing posts, via Twitter OAuth and the like. Oh and I might want to write my own Authorization / Token Endpoint for Micropub, but that’s whole other point.

  • Multilingual / multi topic – This was an itch for me, but this is one of the things I’ve pretty much fixed for the moment. I have multiple feeds of posts, tags, and thanks to indexing it goes fast as well. I might open different blogs on different domains, at some point, and use them like I use Twitter now: post on Seblog and push the post to those blogs. But that’s not a real itch for the moment. It can wait.

All in all there is enough to do, but none of the above things fit in a ‘today I fixed my X’. I need to start doing ‘today I fixed Y of my X’.

As a start of these things, I made a first version for an Indieweb Toolkit. It’s inspired by and makes use of the Kirby Toolkit. I want to put some basic Indieweb stuff in this thing, so I can re-use it for different projects. At this moment it only consists of a Webmention Endpoint discovery function and a wrapper for the php-mf2 Microformats Parser, but more to come!

Day 16: Backing up my Gmail

With the recent events in the USA, I’ve decided that I want to move away from having data on American servers as much as possible. The first thing I want to tackle in that area is my e-mail. Although I do have an address at seblog.nl, I still just redirect it to Gmail.

My computer is currently in the process of backing up my main Gmail. I documented how I do it on the Indieweb Wiki:

Gmvault seems to be very simple and straight forward. It's on the command line, so it's scary for some users, but it does a good job of describing what it does. I did the following on my Mac, and since I can't remember installing pip, I think this works out of the box:

  • sudo pip install --upgrade pip
  • sudo pip install gmvault
  • gmvault sync example@gmail.com
  • Gmvault prompts for OAuth, with a description. Press enter to open the browser, and you have to make sure you are logged in at that browser to the Gmail account.
  • Do the OAuth in de browser and copy the key. Paste it in the Terminal
  • Gmvault does things! I got 6351 mails out of an old account in 16m 14s. It creates a folder called 'gmvault-db' in your home folder, with (in /db/) folders for every month. In those folders are, per e-mail, an '[id].meta' and an '[id].eml.gz'. The .meta is a JSON with info from Gmail (labels/tags, subject) and the .eml.gz is a gzipped .eml, which is just the plain-text e-mail with all the headers.

Having the data is just one step. I will need to think about how I want to manage my e-mail in the future. For now I’m on Gmail still, but I am making plans.

To make today a bit more IndieWeb-relevant (e-mail is not web), I backed Micro.blog, because today is the last day of their Kickstarter campaign.

Although I really like that the project gives Indieweb a lot of attention, it felt wrong to only give to Micro.blog. So I also backed Aaron Parecki with the same amount, for his ongoing 100daysofindieweb. I use a lot of things he made or did first.

Day 15: hacked my own site

Today I hacked my own site. I don't want to give details now, because it's late and it needs a proper write-up, but I will soon. It is fixed now. This post gets updated with a link to a more detailed article when it’s there.

When can one officially put ‘hacker’ in one’s Twitter bio? I think I’m close.

Update: I wrote the post! It's here.

Day 14: XRay like-lookup

I’ve been posting likes to Seblog for a few weeks now. I like the likes, but they al looked like ‘Seb likes this’, so I was like: looks like I like to fix my likes.

Today I hooked up XRay to Seblog, so now my site can see things on URLs I link to. (I actually run a version on a ’secret’ URL, to keep things on my own server.) This way I can grab the name of the post I liked and show that, together with the author. If it has no name, but a photo, it says ‘Seb likes a photo’. It adds the author if it knows who it is. If it really doesn’t know anything about the page, it still defaults to ‘this’. I can fix those manual if I want.

XRay’s format consists of two parts: a data part, with information about the page it looked at, and a refs part, which is a list of URLs that are mentioned or embedded on the page. Retweets, for example, show the original tweet in the refs. I added a refs field to my pages, where I store the data part under the url of the page I mentioned and the refs part under the urls they came with.

An example, with Kirby Data and YAML:

Like-of: http://example.com/a-post


    name: A Post
    author: Someone
    repost-of: http://another-example.com/a-photo
    photo: http://another-example.com/a-photo/img.jpg
    author: Someone Else

I can be more efficient with the moment I grab the data. For example: to send webmentions, my server already reaches out to all links in the post to find their Webmention Endpoint. If I parse the page then, I don’t need another call from XRay later on.

I also need to fiddle a bit with the things I want to display. But for now it’s okay: my server has the data for my likes. I even download pictures if a post has a photo property. I now not only own my likes, I also have my own archive of the things I like.

Day 13: video on Seblog

Today my brother and I made a silly little movie. I used the light on my iPhone to shine through the Apple logo on his MacBook, from the back, while the brightness of his screen was all the way down. All this under a blanked. We had fun and I wanted to share it.

I made a commitment to post everything I post on Instagram or Twitter on my site first, so I used my standard ‘Foto op Seblog’ Workflow to post the video. But it turned out that just adding a video to my photo field didn’t work.

So I did a little tweaking and my site now does support video. In honor of the late Vine I looped it with autoplay. Let’s see how long that lasts.

Day 12: RSVP and Events feed

Busy day today, so no big Indieweb updates. It’s not about the big updates though, it’s about useful updates.

Yesterday, I went to an event. I kind of got into the habit of just getting on my bike and cycling to events, without actually looking up any details at home. Sometimes I don’t even know the exact place I need to go, just that it’s somewhere in the city center. It’s a bad habit, but it’s a habit now.

Since I posted an RSVP to my site for yesterdays event, I was able to find the information I was looking for quickly on my phone. I have been away from Facebook for almost 3 years now and I forgot how useful this feature is.

A combined feed of my RSVPs and Events can now be found at /agenda (Dutch for ‘calendar’), so I can easily find information about where I have to go when I’m cycling and running late next time.

Day 11: a homepage widget

This is a thing I wanted for some time now. Since my weblog is now more a timeline than a blog, long posts disappear fast in the stream of short ones. But since the long posts hold their value longer, I wanted to show them longer.

Also, since I started with this #100daysofindieweb-thingy, I also started to blog in English, which I never did before. My stream of posts gets flooded with English posts.

Today I made a homepage widget. It only shows on the main page, and I can put different parts in it. It now contains a blog list for both Dutch and English, a list of blogposts with #100daysofindieweb, a stream of likes and bookmarks, and the most recent photo post.

This way I can provide a summary of what’s going on on my site. It gives me a framework, where I can always swap parts. The #100daysofindieweb tag, for example, is now a good thing to have, but is less needed 100 days from now.

Days 1 to 10 of #100daysofindieweb

I’ve been doing this #100daysofindieweb thing for 10 days now, so I thought it was time for a summary post. Here are the things I've been working on:


I fixed how things look here on my site quite a lot. On Day 1 I fixed how my reposts look. They are not longer just a url, but now actually show the original post, which I stored during the Twitter dump. I don’t do an automated grab of the external page yet!

I also changed now my RSVPs look on Day 7. First they where just a reply post, with a weird textual representation of ‘yes’ or ‘no’, but now they have icons.

On Day 8 I launched a ‘new post type’. In a way, this is just an different presentation of the posts I write for the purpose of writing them. I now only show the word count on those posts, not the text, because the actual text is less important.

Webmentions plugin

On Day 3, I fixed some bugs regarding relative urls, not only in my own Webmentions plugin, but also in the Kirby Toolkit. It eventually led to a new test on Webmention.rocks.

Today I also added an auto-archive function to my plugin, so it triggers archival copies for any url I mention.

Importing posts

I spend Day 5 and Day 6 importing old posts, which was actually quite hard and is still not really finished. I’m quite picky about how things should look, and they don’t look very good at the moment. There are also a lot of duplicate posts, because I imported my Twitter, and I used to link to my blog on Twitter. I still need to fix these things, but since I already spend two days on this, I postpone it some more.

Markup and headers

On Day 2, I marked up my deleted posts. If a post has a dt-deleted in the past, it returns a 410 Gone header on it’s own page, and shows up as a hidden tombstone in the feed.

And on Day 4 I changed the HTTP header of posts with a dt-published in the future to 404 Not found. Together with hiding them from the feed, this makes scheduled posts.

Finally I fiddled a bit with my Microformats on Day 9, so my site comes out better on Indiecards.

All in all it feels like a productive first 10 days! Only 9 of those sets to go :)

Day 10: auto-archive

Yesterday Tantek encouraged people to trigger an archive in the Internet Archive for every url they mention. It is not that hard to do.

So today I added an auto-archive function to my Kirby webmentions plugin, which you can find here. I’m not aware of anyone using it beside myself, but it is set up more or less so that everyone with a Kirby site can use it. (I am planning some breaking changes though.)

I guess, by posting this to my blog, I trigger an archive to a page describing how to trigger an archive. How meta.

Day 9: invisible things and indiecards

Today I did some invisible things. The first does not count for #100daysofindieweb, because it’s too invisible, but the second is valid, I think.

My blog runs on Kirby, which uses .txt files in a folder structure. In order to show you list posts, my server looks at several .txt files. The more posts I post, the more .txt files my server has to open. It all held up quite a while, but since my Twitter import my site contains 8000 pages.

This was not big of a problem for the main page, because I told Kirby to look at the newest folders first, and stop after 20 posts. (They are sorted by date, after all.) But when you want to view only the blogposts, or only posts with a photo, things became slower, because it had to look further in the past. Not to mention what would happen if you ask for a category that does not exist. With 8000 posts, my server would say no.

So I indexed all my posts with a database, a little while ago, and that seemed fine. The only problem was that the database is a pain to maintain. I did add pages to the database when I posted a post via Micropub, but when I edited pages via FTP, I needed to put ?reindex=1 behind the url to trigger a database update for that page. Sometimes you forget that kind of stuff.

Anyway. Today I made a system where entries are cached. I don’t cache the whole page, I just cache the h-entry, the part in this white block. The page now checks wether a .html version of the entry exists, and also checks the timestamps on the .html, the .txt and the entry.php snippet. If the .html turns out to be current, it shows the .html, and if not, it generates a new .html file and shows that new one.

With this new event, I reindex when the .txt has been updated. I still have to visit the page to trigger a reindex, but it’s much more smooth and almost goes without thinking. I’m very happy with how things turned out.

Unfortunately, all of this is not visible, and I haven’t opensourced this part either. And that’s not how things work in this challenge.

So, I needed to do something more visible for #100daysofindieweb. Yesterday I read about Kevin Marks’s Indiecards, and my site turned out weird on them. The main problem was that Indiecards look for the first h-* element on a page. Before today, the first h-* element on my homepage was a h-feed.

I fixed that today: my homepage now first gives a h-card. My h-entry pages still use that ‘same’ h-card as a p-author. Only my feeds now have a u-author set to /, which translates to https://seblog.nl/, which translates to the h-card on my homepage. More people do this, so I should be fine.

Day 8: the experimental post type ‘wrote’

Yesterday I admitted on IRC that I have quit the #100dagen500woorden part of my challenge. I already stated some doubts but Tantek added me to the wiki anyway.

I felt bad for giving up (so early!) but I felt even worse about posting strange unfinished texts on my site. That’s why I quit: the challenge didn’t feel good.

Today I thought about it some more. I needed a new post-type, where I only post how many words I wrote in a day, not the actual words I wrote.

Compare it with NaNoWriMo, where thousands of people write a novel of 50,000 words in the month of November. The actual text you write in that month does not really matter. The only thing that matters is writing a certain amount of words.

I never participated in NaNoWriMo, so I have no data to export in this format, but I can actually participate this year without having to put my data into NaNoWriMo.org.

Now for the implementation details. I have the following .txt-file on my development server:

This translates to the following public post on my blog:

As you can see, Seblog calculates the amount of words for me, so the only thing I have to do, is write a text and put it under the wrote field. I mark up the number of words with a p-words class, which is probably not consumed by anyone, but hey, I publish this kind of data now.

Now I have no excuse anymore to pick up some writing again.

Last but not least I want to coin the term ‘iceberg post’. A ‘wrote’ in the way I implemented it is an example of an iceberg post: there is a public part and a non-public part to it. It’s just a case of Partial Page Privacy, but I think the term ‘iceberg post’ has a nice ring to it.

Day 7: better RSVP representation

A little while ago I posted my first event, and soon after it came my own RSVP to that event. Back then I wrote a bit of text, because an Indieweb RSVP is a h-entry with a u-in-reply-to and a p-rsvp property. Since it’s a reply, I wrote reply text.

Most people don’t display the text for RSVP posts though, myself included. And today I RSVP’ed on a Meetup.com event, so after the ‘hey you need to post this on your own site first’ bells went off I made a new RSVP on my site. I forgot the text, so it looked like this:

It’s a bit yucky. So since I already have icons here for displaying the different RSVP values (yes, no, maybe and interested), I thought let’s re-use them.

I changed a lot of code behind the scenes too, because my h-entry template became a mess with all the different options it has. (But behind the scenes doesn’t count for #100daysofindieweb!) I now display RSVP’s as what I now call ‘shortposts’:

I also added my bookmarks to the feed at the homepage, because I like to show off my different types of shortposts. (They first where only available at /bookmarks, and I'm actually subscribed to that feed myself, so I can review my own bookmarks in my reader. Nice lifehack.)

Day 6: getting old posts back (part 2)

I got my posts back! :D

Unfortunately the quality of the dump isn't very good. Markdown is sometimes not interpreted, there are double posts (Tweets linking to my blog or Instagram) and a lot of images are at width:500px, which was my default width for over 10 years.

I've been coding some kind of post-match code, too see if I can detect the double posts. It quite hard, because a lot of tweets that are not the same post, are send within 15 minutes of each other, so published is not a good predictor. Trying to calculate the text similarity with levenshtein() seemed like the solution to all my problems, but there are quite a few mismatches still. I probably can't do this fully automated.

I now really have to go to do some real-life stuff, so I guess today just has to be the day I imported the posts and added the images. It's not that I haven't put in the hours today, it's just that it's way too much. (Or I need to be more efficient) You can check things out by scrolling way down in my feed, or at /blog or /tekstbeelden.

Meer laden