BookWyrm uses the ActivityPub protocol to send and receive user activity between other BookWyrm instances and other services that implement ActivityPub, like Mastodon. To handle book data, BookWyrm has a handful of extended Activity types which are not part of the standard, but are legible to other BookWyrm instances.
To view the ActivityPub data for a BookWyrm entity (user, book, list, etc) you can usually add .json
to the end of the URL. e.g. https://www.example.com/user/sam.json
and see the JSON in your web browser or via an http request (e.g. using curl
).
User relationship interactions follow the standard ActivityPub spec.
Follow
: request to receive statuses from a user, and view their statuses that have followers-only privacyAccept
: approves a Follow
and finalizes the relationshipReject
: denies a Follow
Block
: prevent users from seeing one another's statuses, and prevents the blocked user from viewing the actor's profileUpdate
: updates a user's profile and settingsDelete
: deactivates a userUndo
: reverses a Follow
or Block
Move
: communicate that a user has changed their ID and has moved to a new server. Most ActivityPub software will "follow" the user to the new identity. BookWyrm sends a notification to followers and requires them to confirm they want to follow the user to their new identity.Note
: On services like Mastodon, Note
s are the primary type of status. They contain a message body, attachments, can mention users, and be replies to statuses of any type. Within BookWyrm, Note
s can only be created as direct messages or as replies to other statuses.Review
: A review is a status in response to a book (indicated by the inReplyToBook
field), which has a title, body, and numerical rating between 0 (not rated) and 5.Comment
: A comment on a book mentions a book and has a message body.Quotation
: A quote has a message body, an excerpt from a book, and mentions a book.Create
: saves a new status in the database.
Note: BookWyrm only accepts Create
activities if they are:
Note
s with the privacy level direct
, which mention a local user),inReplyToBook
),Delete
: Removes a status
Like
: Creates a favorite on the statusAnnounce
: Boosts the status into the actor's timelineUndo
: Reverses a Like
or Announce
User's books and lists are represented by OrderedCollection
Shelf
: A user's book collection. By default, every user has a to-read
, reading
, and read
shelf which are used to track reading progress.List
: A collection of books that may have items contributed by users other than the one who created the list.Create
: Adds a shelf or list to the database.Delete
: Removes a shelf or list.Add
: Adds a book to a shelf or list.Remove
: Removes a book from a shelf or list.Because BookWyrm uses custom object types (Review
, Comment
, Quotation
) that aren't supported by ActivityPub, statuses are transformed into standard types when sent to or viewed by non-BookWyrm services. Review
s are converted into Article
s, and Comment
s and Quotation
s are converted into Note
s, with a link to the book and the cover image attached.
This may change in future in favor of the more ActivityPub-compliant extended Object types listed alongside core ActivityPub types.
The way BookWyrm sends and receives ActivityPub objects can be confusing for developers who are new to BookWyrm. It is mostly controlled by:
BookWyrm needs to know how to serialize the data from the model into an ActivityPub JSON-LD object.
The /activitypub/base_activity.py
file provides the core functions that turn ActivityPub JSON-LD strings into usable Django model objects, and vice-versa. We do this by creating a data class in bookwyrm/activitypub
, and defining how the model should be serialized by providing an activity_serializer
value in the model, which points to the relevant data class. From ActivityObject
we inherit id
and type
, and two class methods:
to_model
This method takes an ActivityPub JSON string and tries to turn it into a BookWyrm model object, finding an existing object wherever possible. This is how we process incoming ActivityPub objects.
serialize
This method takes a BookWyrm model object, and turns it into a valid ActivityPub JSON string using the dataclass definitions. This is how we process outgoing ActivityPub objects.
A BookWyrm user is defined in models/user.py
:
class User(OrderedCollectionPageMixin, AbstractUser):
"""a user who wants to read books"""
Notice that we are inheriting from ("subclassing") OrderedCollectionPageMixin
. This in turn inherits from ObjectMixin
, which inherits from ActivitypubMixin
. This may seem convoluted, but this inheritence chain allows us to avoid duplicating code as our ActivityPub objects become more specific. AbstractUser
is a Django model intended to be subclassed, giving us things like hashed password logins and permission levels "out of the box".
Because User
inherits from ObjectMixin
, when we save()
a User
object we will send a Create
activity (if this is the first time the user was saved) or an Update
activity (if we're just saving a change – e.g. to the user description or avatar). Any other model you add to BookWyrm will have the same capability if it inherits from ObjectMixin
.
For BookWyrm users, the activity_serializer
is defined in the User
model:
activity_serializer = activitypub.Person
The data class definition for activitypub.Person
is at /activitypub/person.py
:
@dataclass(init=False)
class Person(ActivityObject):
"""actor activitypub json"""
preferredUsername: str
inbox: str
publicKey: PublicKey
followers: str = None
following: str = None
outbox: str = None
endpoints: Dict = None
name: str = None
summary: str = None
icon: Image = None
bookwyrmUser: bool = False
manuallyApprovesFollowers: str = False
discoverable: str = False
hideFollows: str = False
movedTo: str = None
alsoKnownAs: dict[str] = None
type: str = "Person"
You might notice that some of these fields are not a perfect match to the fields in the User
model. If you have a field name in your model that needs to be called something different in the ActivityPub object (e.g. to comply with Python naming conventions in the model but JSON naming conventions in JSON string), you can define an activitypub_field
in the model field definition:
followers_url = fields.CharField(max_length=255, activitypub_field="followers")