Micropubbing with json

If micropub used JSON instead of (/as well as) form encoding, what might submissions look like? Aaaron Parecki brainstormed about this on our Social APIs brainstorming doc. I'm going to repeat his examples for create/update/delete here, compare them with equivalent AS2.0 json, then explore other post 'types'.

Note: I've left out @context from all AS examples because we agreed when missing, this is implied to be "https://www.w3.org/ns/activitystreams".

Create a new post

{
  "object": {
    "content": "hello world",
    "category": ["indieweb"],
    "published": "2015-05-15T13:06:00+02:00",
    "author": "https://rhiaro.co.uk/about#me"
  }
}

No URL is given so it's implied that a new post is being created. Most of this is Aaron's example but I added the explicit published date and author, but these could also be added automatically by the micropub endpoint as the current time and the authenticated user who is sending the request.

ISSUE: actor vs author may never get agreed upon. I think actor is too abstract and author is too specific. I'm a fan of agent to mean 'whatever caused this to happen' (be it human, group, process or bot) but I know that's not an easy sell either. I think one side is going to have to suck it up, and I don't know who this is going to be. AS also has attributedTo which may be a reasonable compromise but seems a bit clunky.

Response per micropub spec: 201 Created and the URL of the post in the Location header.

An AS2.0 Activity for creating a new post would look like:

{
  "@type": "Post",
  "published": "2015-05-15T13:06:00+02:00",
  "actor": "https://rhiaro.co.uk/about#me",
  "object": {
    "content": "hello world",
    "category": ["indieweb"]
  }
}

Response per ActivityPump spec: "This has the side effect that of the object that they are posting is being created." - either I missed something more specific in the spec or this remains to be clarified (I'm not criticising, AP is very much under-development!).

I prefer the first one. Key differences:

There has been a bit of discussion about whether things like audience targeting (to) would be better off attached to the object rather than the activity; I'm inclined to think there's no metadata related to the creating of an object that can't/shouldn't be attached to the object itself, rendering the activity redundant. I keep asking people of examples that contradict this, but so far nobody has given me one.

Update a post

Aaron's micropub example:

{
  "url": "http://example.com/post/1",
  "object": {
    "content": "hello moon"
  }
}

ActivityStreams:

{
  "@type": "Update",
  "object": "http://example.org/post/1"
}

But the latter doesn't contain the updates made to the object... I might be missing something, but there are no specifics in the ActivityPump spec about this; presumably the user updates the object directly and the Update activity is generated from that, which is a curiously object-centric happening in an activity-centric world. Neither contain the updated date; presumably for micropub the endpoint is expected to fill this gap automatically, and possibly in the AS example the activity is meant to have a published property which could also automatically be filled in by the server. I'd like to be explicit about the updated date, though I don't know if this is a must or a should or a may. An alternative I like is:

{
  "object": {
    "@id": "http://example.org/post/1",
    "updated": "2015-05-23T15:00:00+02:00",
    "content": "hello moon"
  }
}

ISSUE: I feel like persuading microformats afficondos to use @id instead of url might be a toughie.

Delete a post

Aaron's example:

{
  "url": "http://example.com/post/1",
  "action": "delete"
}

ActivityStreams:

{
  "@type": "Delete",
  "object": "http://example.org/post/1"
}

They look pleasantly similar :) ActivityPump specifies that "this must leave a shell of the object that will be displayed in activities which reference the deleted object. If the deleted object is requested the server should respond with the HTTP 410 Gone status code."

The empty 'shell' with the deleted date is useful if you want to preserve when you deleted things. Discussed this in #indiewebcamp IRC a bit, and tantek suggested that emptying a post of all metadata but the updated property would suffice for implying a delete. An alternative to the examples above could be:

{
  "object": {
    "@id": "http://example.org/post/1",
    "updated": "2015-05-23T15:00:00+:02:00",
    "...": "...."
  }
}

where "...": "..." means... well, we need to define update behaviour before we can define delete-via-update behaviour. If omitting properties during an update removes them, then we don't need to send empty values for a delete. But if partial updates work, and the properties you send are changed and all others remain the same (which I think is better) then we need to send empty values for everything to delete them. This also allows the possibility of replacing content with say "This post has been deleted" or "this post was removed due to its offensive nature" or "I spelt literally every word wrong and it seemed easier just to forget about it" and so on, which could be useful for smarter display of deleted posts (ie. as normal posts that have been updated to say they've been deleted). I'm sure someone will have something to say about the semantics of delete vs update; if so, please back it up with practical examples/use cases :)

We also need to consider that someone deleting a post may not want to leave a trace, even if just an updated date and simply return a 410 if someone tries to access. This should be allowed, with the caveat that it's not possible to advise other servers that may have consumed the post that it's gone if they don't actively try to retrieve it and see the 410.

Liking a post

Or posting a like. Depending on whether you have object- or activity-tinted glasses. New grounds, so I'll start with what exists in ActivityStreams:

{
  "@type": "Like",
  "published": "2015-05-15T13:06:00+02:00",
  "actor": "https://rhiaro.co.uk/about#me",
  "object": "https://theperplexingpariah.co.uk/2015/05/a-post",
}

Remember in microformats we have the like-of property which would have the same value as object does above, this time, and the 'like' is a first-class post object in itself. So submitting one via micropub could look like:

{
  "object": {
    "published": "2015-05-15T13:06:00+02:00",
    "author": "https://rhiaro.co.uk/about#me"
    "like-of": "https://theperplexingpariah.co.uk/2015/05/a-post"
  }
}

These are preeetttty different with regards to how terms are being used. A more AS-friendly version where the like is still treated as a first-class object would be:

{
  "@type": "Like",
  "published": "2015-05-15T13:06:00+02:00",
  "actor": "https://rhiaro.co.uk/about#me",
  "object": "https://theperplexingpariah.co.uk/2015/05/a-post",
  "result": {
    "published": "2015-05-15T13:06:00+02:00",
    "author": "https://rhiaro.co.uk/about#me"
  }
}

Even though a like post is implicitly created through the result you have to go via the activity that generated it to find out what it's a like of so it doesn't really stand on its own. In the same way as audience targeting and other metadata is useful attached to the object I think having the like relation directly between the objects is practical. For semantic pedants, one post is a like-of another post, so it's closer to having a like type than being a like verb. But it's an implicit type, rather than an explicit one. Just to reiterate: I'm not saying 'post A likes post B'. That would be silly. 'Post A like-of post B' pretty easily implies the author of post A likes post B. It's not worth trying to overthink that, and you don't need RDF inferencing to make sense of it.

In summary, even for things other than creating posts, I think just sending an object works. You can attach other things like "content": "I like Jessica's post" too if you wanted, for nicer display.

Checkin

The closest example in ActivityStreams is an Arrive activity:

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "@type": "Arrive",
  "actor": {
    "@type": "Person",
    "displayName": "Sally"
  },
  "location": {
    "@type": "Place",
    "displayName": "Work"
  },
  "origin": {
    "@type": "Place",
    "displayName": "Home"
  }
}

I have never needed origin with a checkin so I'm going to ignore that for now. I think I want to send to my micropub endpoint something more like:

{
  "object": {
    "published": "2015-05-23T13:00:00+02:00",
    "location": "https://rhiaro.co.uk/home",
    "content": "I got home"
  }
}

or

{
  "object": {
    "published": "2015-05-23T13:00:00+02:00",
    "location": "http://dbpedia.org/resource/Edinburgh",
    "content": "Finally back in Auld Reekie!"
  }
}

These are just like regular posts, but with a location property. Which is magically compatible with ActivityStremas and Microformats!

Where location is always a URI, so that display name, latitude, longitude, etc, can be attached to that and don't need to be included in the checkin. I'm a big fan of not nesting. If you submit a checkin with a nested venue object, the endpoint has to try to create the venue before it can do the checkin, which means it needs to attempt to de-dup venues as part of the checkin process; and if the creation fails then there's more hassle... there's probably a smart way of designing the UI plus use of javascript that can make this pretty smooth, but it's definitely not something I want to have to deal with.

RSVP

In AS we have Accept which is supposed to have an object of Invite which in turn has an object of Event (whether these are all nested in the Accept activity or just referenced by URI doesn't really matter, the structure is the same. I included the nesting here cos I just copied the example directly from the AS vocabulary spec):

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "@type": "Accept",
  "actor": {
    "@type": "Person",
    "displayName": "Sally"
  },
  "object": {
    "@type": "Invite",
    "actor": "http://john.example.org",
    "object": {
      "@type": "Event",
      "displayName": "A Party!"
    }
  }
}

While it's all semantically-pedantically very lovely, I reckon the microformats way of just having an RSVP be a regular post with a reply-to another regular post which happens to have a location, start and end (thus making it an event) is easier to follow and generally simpler (and effective as evidenced by all the people posting and RSVPing to indie events). In JSON, I think that'd look like:

{
  "object": {
    "published: "2015-05-23T13:00:00+02:00",
    "reply-to": "http://indiewebcamp.com/2015/Edinburgh",
    "rsvp": "Yes",
    "content": "Really looking forward to IWC Edinburgh!"
  }
}

Microformats does have invitations too though - what's the fun of RSVPing to an event if you can't invite your friends! Invite posts can be part of an event post, part of an RSVP post, or a post by themselves. That's because all we're really doing to create them is adding invitee properties for everyone you want to invite, and then sending them webmentions. If your invitation is not also the original event, you can send the original event post a webmention too (since you've mentioned it, after all) which could allow it to update its own list of who has been invited. Your invitee should reply with their RSVP both to your invitation and the event (which they can do with one post). Brainstorming about this, which is by no means sent in stone, on IWC wiki.

Here is an event with a list of invitees:

{
  "object": {
    "published: "2015-05-23T13:00:00+02:00",
    "name": "IndieWebCamp Edinburgh",
    "start": "2015-07-25T10:00:00+01:00"
    "end": "2015-07-26T18:00:00+01:00",
    "location": "http://dbpedia.org/resource/Edinburgh",
    "content": "Bring your friends to IWC Edinburgh!",
    "invitee": ["http://tantek.com", "http://theperplexingpariah.co.uk", "http://aaronpk.com"]
  }
}

Consuming

Neither ActivityStreams nor Micorformats have things to do with eating and drinking, but this is something I log on my site. My micropub endpoint currently recognises Aaron's p3k-food property so I'll use that here, but I'd prefer it to be something more generic..

{
  "object": {
    "published": "2015-05-23T17:00:00+02:00",
    "p3k-food": "A burrito",
    "category": ["mexican","burrito","yum"]
  }
}

A potentially useful fall back for something that wants to display this post but doesn't understand p3k-food could just to be to treat it as content.

Yes, I do desire to tag my food posts.

The value of p3k-food could also be a handy URI for the food, if one existed, which when dereferenced could give you things like different language display names, calorie count, cuisine affiliations, ingredients, etc.

Conclusion

My micropub endpoint will accept JSON and expect an object with a bunch of properties. From these properties my server can infer how to treat them with regards to who to send webmentions to (ie. who to explicitly notify that this object was created; if I was implementing ActivityPump I'd send the whole object to these people's inboxes) and how to display them. It dosen't need explicit types, or properties spread over activities and objects to do this.

In anther post I'll run through this for (un)follows, adding to and removing from collections, exercise posts, music listens, code commits, travel plans, offers and requests, and see if they hold up the same.

If you have examples of online social stuff you do that you don't think you could publish and propagate this way, please tell me all about it. I really want to know.

🏷 http://vocab.amy.so/blog#Doing http://vocab.amy.so/blog#Doing activitystreams as2 brainstorming hacking idea indieweb json microformats2 micropub slogd social web socialwg