I’ve previously written about the ActivityPub communication that happens when someone wants to follow a community on a remote instance, because that’s what I was coding at the time. A week later, things have moved on a lot and one of the tasks completed recently was the opposite – getting a user on a remote instance to follow one on my PieFed instance. In theory this should be the same but this time the remote instance was the one initiating it so the way things played out was a bit different.
The remote instance is running Lemmy 0.18. As well as the basic WebFinger+AP communication described in my earlier blog post, Lemmy makes a few extra GET requests once their Follow has been Accepted. All the following GETs have the ‘Accept’ header set to ‘application/activity+json’. If you’re trying these out in Postman, don’t forget to set this header!
Moderators
It’s important to know who the moderators of a community are as they are shown in the right hand column of that community. To find this out, Lemmy does:
GET /c/community_name/moderators
This returns a list of moderators for the community:
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
],
"type": "OrderedCollection",
"id": "https://lemmy.nz/c/offtopic/moderators",
"orderedItems": [
"https://lemmy.nz/u/Dave",
"https://lemmy.nz/u/cloventt"
]
}
OrderedCollection is a standard AP thing but I would have expected it to contain a bit more than just a list of strings in it’s orderedItems, like Actor objects. To display the moderator’s profile pictures (for example), the recipient needs to turn those URLs into Actor objects by doing a WebFinger and then a GET /u/actor_name for each, which seems inefficient to me. Luckily most communities have only one or two moderators.
Kbin returns “{}” – it does not federate this information. This makes it impossible for Kbin instances to validate remote deletion requests from moderators on the originating instance but Kbin ignores such requests anyway so it’s moot.
Community Outbox
Initially when you first start following someone on Mastodon, etc their profile may not show any of their past posts as your server has not been sent any of them yet. Kbin suffers from this too and initially communities (which Kbin calls “magazines”) are empty. Lemmy sensibly makes a request to /c/community_name/outbox, which contains all the content in that community, presumably to ingest it (I haven’t verified this as at the time I saw the GET arrive I didn’t have any code that could handle that request).
The return value is an OrderedCollection of up to 50 nested AP objects:
Announce {
Create {
Page {
}
}
}
Unlike /moderators, /outbox is widely used by nearly all fediverse software (except for Kbin which returns “{}” like a psychopath) so it’s structure is much more how I expected.
IMO ‘Announce’ is not really needed and doesn’t add any new information. Mastodon 4.2.x outboxes are just an OrderedCollection of Create -> Note (aside: Lemmy uses Note too – for replies to posts (which are Page objects in Lemmy)). Another thing that Mastodon does nicely is each Note has a ‘replies’ Collection on it with a URL to query to get all the replies – PieFed might have to copy that idea!
/
Lemmy also makes a GET of the root of the instance, which returns a ‘Application‘ AP Object, describing basic info about the instance. It’s basically an Actor object except talking about the instance as a whole rather than an individual account. Name, profile picture, cover picture, etc. It even has a public key like an Actor would, for some reason!
Once a Lemmy instance has federated with another it starts regularly polling the following two endpoints. It does this every 30 minutes, which seems much too often to me.
/nodeinfo/2.0.json
This returns basic statistical information about the instance, something like
{
"version": "2.0",
"software": {
"name": "lemmy",
"version": "0.18.5"
},
"protocols": [
"activitypub"
],
"usage": {
"users": {
"total": 816,
"activeHalfyear": 312,
"activeMonth": 84
},
"localPosts": 3128,
"localComments": 19888
},
"openRegistrations": true
}
/api/v3/site
This is similar to the two endpoints above but much more in depth. It lists the admins of the site, various settings the site has (nsfw allowed, downvotes enabled, registrations open, etc):
{
"site_view": {
"site": {
"id": 1,
"name": "Lemmy NZ",
"sidebar": "text goes here",
"published": "2023-06-02T09:46:21.972257",
"updated": "2023-11-03T03:22:35.594456",
"icon": "https://lemmy.nz/pictrs/image/sdf.png",
"banner": "https://lemmy.nz/pictrs/image/68bac1.png",
"description": "Lemmy for New Zealanders",
"actor_id": "https://lemmy.nz/",
"last_refreshed_at": "2023-06-02T09:46:21.960383",
"inbox_url": "https://lemmy.nz/site_inbox",
"public_key": "blah blah",
"instance_id": 1
},
"local_site": {
"id": 1,
"site_id": 1,
"site_setup": true,
"enable_downvotes": true,
"enable_nsfw": true,
"community_creation_admin_only": true,
"require_email_verification": false,
"application_question": "blah blah",
"private_instance": false,
"default_theme": "browser",
"default_post_listing_type": "All",
"hide_modlog_mod_names": true,
"application_email_admins": true,
"actor_name_max_length": 20,
"federation_enabled": true,
"captcha_enabled": true,
"captcha_difficulty": "medium",
"published": "2023-06-02T09:46:22.153520",
"updated": "2023-11-03T03:22:35.600601",
"registration_mode": "RequireApplication",
"reports_email_admins": true
},
"local_site_rate_limit": {
"id": 1,
"local_site_id": 1,
"message": 999,
"message_per_second": 60,
"post": 50,
"post_per_second": 600,
"register": 20,
"register_per_second": 3600,
"image": 100,
"image_per_second": 3600,
"comment": 100,
"comment_per_second": 600,
"search": 999,
"search_per_second": 600,
"published": "2023-06-02T09:46:22.156933"
},
"counts": {
"id": 1,
"site_id": 1,
"users": 816,
"posts": 3128,
"comments": 19890,
"communities": 7,
"users_active_day": 21,
"users_active_week": 51,
"users_active_month": 84,
"users_active_half_year": 312
}
},
"admins": [
{
"person": {
"id": 2,
"name": "Dave",
"avatar": "https://lemmy.nz/pictrs/image/5eb39c6b-a1f0-4cba-9832-40a5d8ffb76a.png",
"banned": false,
"published": "2023-06-02T09:46:20.302035",
"actor_id": "https://lemmy.nz/u/Dave",
"local": true,
"deleted": false,
"matrix_user_id": "@bechorin:matrix.org",
"admin": true,
"bot_account": false,
"instance_id": 1
},
"counts": {
"id": 1,
"person_id": 2,
"post_count": 168,
"post_score": 1495,
"comment_count": 2669,
"comment_score": 10503
}
},
{
"person": {
"id": 15059,
"name": "idanoo",
"banned": false,
"published": "2023-06-08T22:13:43.366681",
"actor_id": "https://lemmy.nz/u/idanoo",
"local": true,
"deleted": false,
"matrix_user_id": "@idanoo:mtrx.nz",
"admin": true,
"bot_account": false,
"instance_id": 1
},
"counts": {
"id": 6544,
"person_id": 15059,
"post_count": 0,
"post_score": 0,
"comment_count": 5,
"comment_score": 10
}
}
],
// supported languages snipped - it's 1000 more lines of json
}
PieFed will probably need some of this data, but probably not every 30 minutes. Perhaps it’ll call these endpoints once per day.
Note how datetimes in these examples have no timezone. At other times, Lemmy will reject datetimes without +00:00 (UTC timezone) on the end.
So that’s fun.
> GET /c/community_name/moderators
I think this URL is actually returned as a part of the JSON response you get when you fetch a community? So it doesn’t have to strictly be that.
Oh yes, there it is, under “attributedTo”. Good spotting, thanks.
Kbin uses a “moderators” attribute for this, it turns out.
Lemmy maintainer here, its very interesting to read all this from the perspective of a new developer. However Im quite surprised that you say Lemmy requests nodeinfo and /api/v3/site every 30 minutes from remote instances because that shouldnt happen. In fact nodeinfo is only called once a day to check if the instance is alive (its fine if the endpoint doesnt exist, as long as it doesnt return a server error). /api/v3/site should never be called on any remote instance.
Strange! Maybe other software is calling that endpoint, too. I’m now getting two requests there, every 30 minutes. Also something is hitting /nodeinfo/2.0.json twice every 30 mins (that one varies a lot, sometimes after 10 minutes).
I just managed to intercept a request to nodeinfo and the user agent in the header was ‘lemmy-stats-crawler’.
Anyway these are not expensive / heavy endpoints to serve so I’m not too concerned. Just some easily-cachable stats.
Ah that crawler is a separate project, its used to collect information about Lemmy instances. Its completely unrelated to federation, so you dont need to implement these endpoints. For one thing the crawler is used to build the instance list on join-lemmy.org, and I suppose some other people run it as well, either manually or on cronjobs.
https://github.com/LemmyNet/lemmy-stats-crawler/
The nodeinfo endpoint is generally used to collect stats about federated software, so you should implement it to get listed on https://the-federation.info/ or https://fediverse.observer/
I see. Thank you very much!