A (tiny, incomplete, single user, write-only) ActivityPub server in PHP


I've written an ActivityPub server which only allows you to post messages to your followers. That's all it does. It won't record favourites or reposts. There's no support for following other accounts or receiving replies. It cannot delete or update posts nor can it verify signatures. It doesn't have a database or any storage beyond flat files.

But it will happily send messages and allow itself to be followed.

This shows that it is totally possible to broadcast fully-featured ActivityPub messages to the Fediverse with minimal coding skills and modest resources.

Why

I wanted to create a service a bit like FourSquare. For this, I needed an ActivityPub server which allows posting geotagged locations to the Fediverse.

I didn't want to install a fully-featured server with lots of complex parts. So I (foolishly) decided to write my own. I had a lot of trouble with HTTP Signatures. Because they are cursed and I cannot read documentation. But mostly the cursed thing.

How

Creating a minimum viable Mastodon instance can be done with half a dozen static files. That gets you an account that people can see. They can't follow it or receive any posts though.

I wanted to use PHP to build an interactive server. PHP is supported everywhere and is simple to deploy. Luckily, Robb Knight has written an excellent tutorial, so I ripped off his code and rewrote it for Symfony.

The structure is relatively straightforward.

  • /.well-known/webfinger is a static file which gives information about where to find details of the account.
  • /[username] is a static file which has the user's metadata, public key, and links to avatar images.
  • /following and /followers are also static files which say how many users are being followed / are following.
  • /posts/[GUID] a directory with JSON files saved to disk - each ones contains the published ActivityPub note.
  • /photos/ is a directory with any uploaded media in it.
  • /outbox is a list of all the posts which have been published.
  • /inbox is an external API endpoint. An ActivityPub server sends it a follow request, the endpoint then POSTs a cryptographically signed Accept message to the follower's inbox. The follower's inbox address is saved to disk.
  • /logs is a listing of all the messages received by the inbox.
  • /new is a password protected page which lets you write a message. This is then sent to...
  • /send is an internal API endpoint. It constructs an ActivityPub note, with attached location metadata, and POSTs it to each follower's inbox with a cryptographic signature.

That's it.

The front-end grabs my phone's geolocation and shows the 25 nearest places within 100 metres. One click and the page posts to the /send endpoint which then publishes a message saying I'm checked in. It is also possible to attach to the post a short message and a single photo with alt text.

There's no database. Posts are saved as JSON documents. Images are uploaded to a directory. It is single-user, so there is no account management.

What Works

  • Users can find the account.
  • Users can follow the account and receive updates.
  • Posts contain geotag metadata.
  • Posts contain a description of the place.
  • Posts contain an OSM link to the place.
  • Posts contain a custom message.
  • Posts autolink #Hashtags (sort of).
  • Posts can have an image attached to them.
  • Messages to the inbox are recorded (but not yet integrated).

ToDo

  • My account only has a few dozen followers, some of whom share the same sever. Even with cURL multi handle, it takes time to post to several servers.
  • It posts plain text. It doesn't autolink websites
  • Hashtags are linked when viewed remotely, but they don't go anywhere locally.
  • There's no language selection - it is hard-coded to English.
  • The outbox isn't paginated.
  • The UI looks crap - but it is only me using it.
  • There's only a basic front-page showing a map of all my check-ins.
  • Replies are logged, but there's no easy way to see them.
  • Doesn't show any metadata about the place being checked-in to. It could use the item's website (if any) or hashtags for the type of amenity it is.
  • No way to handle being unfollowed.
  • No way to remove servers which have died.
  • Probably lots more.

Other Resources

I found these resources helpful while creating this project:

What's Next?

I've raised an issue on Mastodon to see if they can support showing locations in posts. Hopefully, one day, they'll allow adding locations and then I can shut this down.

The code needs tidying up - it is very much a scratch-my-own-itch development. Probably riddled with bugs and security holes.

World domination?

Where

You can laugh at my code on GitHub.

You can look at my check-ins on a map.

You can follow my location on the Fediverse at @edent_location@location.edent.tel


Share this post on…

  • Mastodon
  • Facebook
  • LinkedIn
  • BlueSky
  • Threads
  • Reddit
  • HackerNews
  • Lobsters
  • WhatsApp
  • Telegram

10 thoughts on “A (tiny, incomplete, single user, write-only) ActivityPub server in PHP”

  1. says:

    @blog this is great!! Nice work. 🤘

    While I appreciate that you don't want to run a server forever, I hope that Mastodon doesn't implement a similar location service; not because I don't think it's a good or fun idea, but because I think the fediverse would be better off with a diversity of tools.

    ActivityPub is an extensible format, designed to be adapted and used in ways the authors never imagined, let alone intended. I think what you've built is a powerful example of that! Embrace it! ❤️

    | Reply to original comment on mastodon.social
  2. said on mastodon.social:

    @Edent @edent_location Howdy. I'm writing an ActivityPub C2S client web app, so I used your location notes to prototype a tag in my UI. Here's what it currently looks like. (Not sure I like showing the co-ordinates - I may hide them in future. Also, the tag is a clickable link that goes to <a href="https://www.openstreetmap.org/?mlat=" rel="nofollow noopener noreferrer"><span>https://www.</span><span>openstreetmap.org/?mlat=</span><span></span></a>${latitude}&amp;mlon=${longitude}<a href="https://mastodon.social/tags/map" rel="tag">#<span>map</span></a>=19/${latitude}/${longitude}) map OpenStreetMap

    Reply | Reply to original comment on mastodon.social
  3. Great effort!

    Regarding the logic to find locations: I think the really scalable approach here is to list all but SORT them based on which locations has been checked in the most. I think this follows the Swarm logic more closely. If people want to check in at a park bench near Notre Dame, the park bench should show first, not Notre Dame. This is a database you would have to run, but I think this is a quite useful database to license publicly: it indicate what people are interested in, world-wide. To work, people checking in should permit eg CC0 usage of their location data for this purpose.

    Cheers, Simon

    Reply

Trackbacks and Pingbacks

What are your reckons?

All comments are moderated and may not be published immediately. Your email address will not be published.

Allowed HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <p> <pre> <br> <img src="" alt="" title="" srcset="">