---
title: "FediLeventy Part 2: comments and follower lists"
date: 2022-11-09T00:00:00
slug: fedileventy-part-2-comments-and-follower-lists

---
Yes, I'm calling it FediLeventy and there's nothing any of you can do to stop me.

Yesterday, after publishing my [post on implementing some features of ActivityPub](https://lewisdale.dev/post/you-can-be-friends-with-my-blog), I received a comment from [i@social.bennypowers.dev](https://social.bennypowers.dev/@i):

![i@social.bennypowers.dev - 8 Nov 2022 - oo oo oo can we do comments via mastodon replies?](./src/images/mastodon-comment-1024x523.png)

The short answer: No. The long answer: Not yet.

## Yet is now

Given I already had most of the framework in place for handling comments, it was actually really straightforward. A comment is a `Create` activity, and looks something like this (from [the W3C document outlining the spec](https://www.w3.org/TR/activitypub/#object-without-create)):

```json
{
  "@context": "https://www.w3.org/ns/activitystreams",
  "type": "Create",
  "id": "https://example.net/~mallory/87374",
  "actor": "https://example.net/~mallory",
  "object": {
    "id": "https://example.com/~mallory/note/72",
    "type": "Note",
    "attributedTo": "https://example.net/~mallory",
    "content": "This is a note",
    "published": "2015-02-10T15:04:55Z",
    "to": ["https://example.org/~john/"],
    "cc": ["https://example.com/~erik/followers",
           "https://www.w3.org/ns/activitystreams#Public"]
  },
  "published": "2015-02-10T15:04:55Z",
  "to": ["https://example.org/~john/"],
  "cc": ["https://example.com/~erik/followers",
         "https://www.w3.org/ns/activitystreams#Public"]
}
```

It's fairly simple to convert this into a format that I could then display as a comment: I just store the display name for the Actor (i.e. username@domain), a flag to say it's a Mastodon comment, a link to the commenter, and the original reply. With that, I just added some extra fields for things like linking to the original status, and the user that posted the comment.

### Comment content

One minor point I had to deal with is that the content field can contain HTML - in fact all Mastodon replies seem to. I handle this by first adding in line break characters where the `<p>` tags close, and then replacing all other HTML tags:

```javascript
{
	comment: body.object.content
		.replaceAll('</p>', '\n')
		.replace(/<[^>]+>/g, '')
}
```

and then when rendering, I just replace my line break characters with `<br>` tags. It's pretty simple, and definitely imperfect, but works well enough.

{#% image "./img/mastodon-comment.png", "Blog comments section. Last comment reads: 'I made this comment via Mastodon!'", "mx-auto h-auto" %#}

## Follower lists

This one was much simpler: I want accurate follower counts when users view the blog's profile via their instance. For that to happen, I need the `/followers` endpoint defined.

The JSON looks like this:

```json
{
  "@context": "https://www.w3.org/ns/activitystreams",
  "id": "https://lewisdale.dev/followers",
  "type": "OrderedCollection",
  "totalItems": 5,
  "first": "https://lewisdale.dev/followers?page=1"
}
```

which I render with a new Edge Function, routed to [https://lewisdale.dev/followers](https://lewisdale.dev/followers). It handles two cases: the above, where there is no page parameter, and a second, where the page is included.

In both cases I load all of my followers from my database, and if there's no page requested I just output the count. If the page has been requested, I paginate the list with pages of 10, and then add next and previous links where appropriate. Here's some example output:

```json
{
  "@context": "https://www.w3.org/ns/activitystreams",
  "id": "https://lewisdale.dev/followers",
  "type": "OrderedCollection",
  "totalItems": 5,
  "partOf": "https://lewisdale.dev/followers",
  "orderedItems": [
    "https://dapchat.online/users/lorcol",
    "https://dapchat.online/users/charlie",
    "https://dapchat.online/users/lewisdaleuk",
  ]
}
```

## Delete requests

This was an interesting one, I wasn't really expecting to receive Delete requests, but apparently if a user deletes their account, their instance may send them out to every instance it's aware of, essentially asking them to delete any information they have for them. In my case, this just means dropping any comments with that actor. Simple enough!

## Next steps

Things I still want to do:

* Track reblogs so that I can attach a "Reblogged n times" field to each post
* Improve formatting of post notifications
* General tidying up and publishing reproducable source
* Handle comment chains gracefully - right now we just ignore them
