blog, (edited )
@blog@shkspr.mobi avatar

A (tiny, incomplete, single user, write-only) ActivityPub server in PHP
https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/

I've written an ActivityPub server which . 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

https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/

#ActivityPub #fediverse #mastodon #php #Symfony

blaine,
@blaine@mastodon.social avatar

@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! ❤️

Edent,
@Edent@mastodon.social avatar

@blaine @blog
That's a fair point. But I think I disagree.

I don't want a separate account for FourSquare, Untappd, BookWyrm, etc. I'd rather have one account with many channels.

But, as you say, it is good to have a diversity of extensible tools.

blaine,
@blaine@mastodon.social avatar

@Edent oh! I totally agree on that point. For me, one of the biggest missing pieces so far is that webfinger was always intended to be a "one account, multiple endpoint" system. So, you'd have e.g 'edent@shkspr.mobi' and that would point at various AP (or other!) feeds with appropriate content types.

Mastodon's kind of squatted on the whole namespace and tied webfinger IDs to individual servers, which is unfortunate (but not unfixable!)

blaine,
@blaine@mastodon.social avatar

@Edent let me know if that makes sense as stated or needs more fleshing out! I've never been accused of being too verbose in my written explanations of these things, so if that didn't make sense, it's me, not you. 😅

boris,

@Edent @blaine @blog I think we can interop through a form of cross posting.

Effectively, shove stuff down into a “Note” representation for Mastodon and other protocols that mostly understand microblogging.

But also emit “Place” or “Checkin” or whatever nouns for systems that can understand that natively.

And of course that’s just for servers. Clients can evolve more quickly and understand / display different nouns.

blaine,
@blaine@mastodon.social avatar

@boris @Edent @blog yup! But I think also multiple feeds; a good example might be your food wiki – cross-posting every update you make there to boris@cosocial.ca would be ... frustrating ... for most of your followers, but exposing a feed so that folks who know you're boris@cosocial.ca and also cook can find it easily? :chefskiss:

boris,

@blaine @Edent @blog yes / no.

It’s not the best example because that’s just a standard Note noun again. A Recipe noun is a better example to think through.

I can’t “reply” as my FoodWiki which is the too many accounts problem again.

Somewhere in here we end up with Jaiku ;)

mat,

@blaine @blog I'm still reeling from the news that servers filter out fields they don't recognise. That seems to kill extensibility dead. You're reduced to begging each platform to support your idea. In practice, Eugen decides. Like, is that really how it was meant to work?

mastodon.social/

For the record, I should have known, of course Friendica does the same thing. It way predates AP and has nowhere in the schema to store extra fields. I could remedy that with a plugin, but without a norm what would be the point?

Edent,
@Edent@mastodon.social avatar

@mat @blaine
I think it is a bit like Postel's Law. Servers will happily receive anything - but they will only send out what they understand or deem safe.

For example, I could embed a <script> tag in my content, but most servers will filter that out.

As an admin, I'd be wary about passing on a type, object, or activity that I wasn't 100% sure clients would understand.

So, I agree with you in theory. But I understand why the current practise is different.

RedtheBean,

@blog @edent_location dude this blog is my holy grail

Edent,
@Edent@mastodon.social avatar

@RedtheBean I live to serve 😃

chris,
@chris@mstdn.chrisalemany.ca avatar

@blog this is really cool! Well done!

  • All
  • Subscribed
  • Moderated
  • Favorites
  • fediverse
  • DreamBathrooms
  • magazineikmin
  • ethstaker
  • InstantRegret
  • tacticalgear
  • rosin
  • love
  • Youngstown
  • slotface
  • ngwrru68w68
  • kavyap
  • cubers
  • thenastyranch
  • mdbf
  • provamag3
  • modclub
  • GTA5RPClips
  • normalnudes
  • khanakhh
  • everett
  • cisconetworking
  • osvaldo12
  • anitta
  • Leos
  • Durango
  • tester
  • megavids
  • JUstTest
  • All magazines