diff --git a/bookwyrm/activitypub/note.py b/bookwyrm/activitypub/note.py
index ebc0cf3c..f4a22c41 100644
--- a/bookwyrm/activitypub/note.py
+++ b/bookwyrm/activitypub/note.py
@@ -63,3 +63,9 @@ class Quotation(Comment):
''' a quote and commentary on a book '''
quote: str
type: str = 'Quotation'
+
+@dataclass(init=False)
+class Progress(Comment):
+ ''' a progress update on a book '''
+ quote: str
+ type: str = 'Progress'
diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py
index 09ceda85..c56fa395 100644
--- a/bookwyrm/models/status.py
+++ b/bookwyrm/models/status.py
@@ -201,6 +201,35 @@ class Quotation(Status):
activity_serializer = activitypub.Quotation
pure_activity_serializer = activitypub.Note
+class Progress(Status):
+ ''' an update of where a user is in a book, using page number or % '''
+ class ProgressMode(models.TextChoices):
+ PAGE = 'PG', 'page'
+ PERCENT = 'PCT', 'percent'
+
+ progress = models.IntegerField()
+ mode = models.TextChoices(max_length=3, choices=ProgessMode.choices, default=ProgressMode.PAGE)
+ book = models.ForeignKey('Edition', on_delete=models.PROTECT)
+
+ @property
+ def ap_pure_content(self):
+ ''' indicate the book in question for mastodon (or w/e) users '''
+ if self.mode == ProgressMode.PAGE:
+ return 'on page %d of %d in "%s"' % (
+ self.progress,
+ self.book.pages,
+ self.book.remote_id,
+ self.book.title,
+ )
+ else:
+ return '%d%% of the way through "%s"' % (
+ self.progress,
+ self.book.remote_id,
+ self.book.title,
+ )
+
+ activity_serializer = activitypub.Progress
+ pure_activity_serializer = activitypub.Note
class Review(Status):
''' a book review '''