We will have to parse the @context of incoming messages properly. When other libraries call django_kepi.take(), they pass a dict of the same format as would be received from a client. "Addressing fields" are to, bto, cc, bcc, and audience. Check what integration tests Mastodon runs for Activities. Is it reasonable to have multiple Activity-type handling classes representing a single object? == From django_kepi.take() "attributedTo" and "type" must be set. Check blocking, and reject if they're blocked. Strip the "id" and generate our own. Store Activity (which will add it to the user's outbox. Perform side-effects. Perform delivery. Call activity_notify on relevant objects. == From client If the type is a valid ActivityStreams type but not an Activity, wrap it in a Create activity before continuing. Check blocking, and reject if they're blocked. Strip the "id" and generate our own. Return 201 Created, Location: (our id). Set "attributedTo" to the client's ID. Store Activity (which will add it to the user's outbox. Perform side-effects. Perform delivery. Call activity_notify on relevant objects. == From server Check blocking, and reject if they're blocked. Store Activity. Perform side-effects. Forward, if necessary. Call activity_notify on relevant objects. == From background processing Check the UUID to see what to do. XXX What are the options? - We wanted this file because it was referenced in an Activity (such as is needed by the policy of trusting nobody) - We wanted this file because it describes the target of a particular delivery Note that we can't just deliver the moment we've found details of an Actor. If there are many of them, we might choose to deliver to their sharedInbox instead. ===== Delivery Make a list of recipients: the union of "to", "bto", "cc", "bcc", and "audience". XXX We will really need caching here. XXX But cache the contents of collections according to the user requesting them. Also, delivery needs to be done as a background task. ===== Blocking ... ===== Side-effects == Create The new object is in "object". Copy the addressing fields from the Activity to the object, and vice versa. Send the object to the activity_create class method of each of the delegates listed. If any returns None, go on to the next one. If they all return None, we have to reject the activity. When a delegate returns non-None, that's the id of the object. == Update Find the object listed in "object" -> "id". Call that object's activity_update method. Pass partial=False from server, partial=True (the default) from client. In either case, don't pass in the "id" field. == Delete Find the object listed in "object" -> "id". Call that object's activity_delete method. == Follow ... == Accept If the activity linked in "object" is known, set its "followResult" to "accepted". == Reject If the activity linked in "object" is known, set its "followResult" to "rejected". == Like none == Block From client: no immediate side-effect. From server: reject. == Announce Can we get these from the client? == Undo Activities we don't accept: Create, Delete, Add, and Remove. I don't know whether we can undo an Undo, but let's assume not. We don't accept undo for Update because we don't keep records of the previous state. == other Non-Activities that we can accept from a client: Article Audio Document Event Image Note Page Place Profile Relationship Tombstone Video Non-Activities from a server get rejected. ===== Models == Activity == Tombstone If a django_kepi.find() finds no response, we check Tombstone. Records found therein are returned with 410 Gone. == Following This is complicated enough to derive from the Activity objects that ...?