bookwyrm/bookwyrm/activitypub/verbs.py
Mouse Reeve 779d2b0694 Attempt to complete inbox requests synchronously
When an inbox activity comes in from another fediverse instance, the
behavior prior to this commit was always to immediately give a 200
response to the external server and then create a celery activity
(usually in the MEDIUM_PRIORITY queue) to complete it.

Instead, this would receive a request and try to complete it without
making any http requests (which would make the request take too long to
process). If an external request is required to complete the activity, a
task is created and added to the queue.

Ideally, this will cause some tasks to happen very promptly, and reduce
the load on celery, which would help queued tasks happen more quickly as
well.

One downside is that this will make completing http requests from
external servers slowing (since it's doing a bunch of thinking before
responding).
2023-02-20 11:05:18 -08:00

234 lines
6.4 KiB
Python

""" activities that do things """
from dataclasses import dataclass, field
from typing import List
from django.apps import apps
from .base_activity import ActivityObject, Signature, resolve_remote_id
from .ordered_collection import CollectionItem
@dataclass(init=False)
class Verb(ActivityObject):
"""generic fields for activities"""
actor: str
object: ActivityObject
def action(self, allow_external_connections=True):
"""usually we just want to update and save"""
# self.object may return None if the object is invalid in an expected way
# ie, Question type
if self.object:
self.object.to_model(allow_external_connections=allow_external_connections)
# pylint: disable=invalid-name
@dataclass(init=False)
class Create(Verb):
"""Create activity"""
to: List[str]
cc: List[str] = field(default_factory=lambda: [])
signature: Signature = None
type: str = "Create"
# pylint: disable=invalid-name
@dataclass(init=False)
class Delete(Verb):
"""Create activity"""
to: List[str] = field(default_factory=lambda: [])
cc: List[str] = field(default_factory=lambda: [])
type: str = "Delete"
def action(self, allow_external_connections=True):
"""find and delete the activity object"""
if not self.object:
return
if isinstance(self.object, str):
# Deleted users are passed as strings. Not wild about this fix
model = apps.get_model("bookwyrm.User")
obj = model.find_existing_by_remote_id(self.object)
else:
obj = self.object.to_model(
save=False,
allow_create=False,
allow_external_connections=allow_external_connections,
)
if obj:
obj.delete()
# if we can't find it, we don't need to delete it because we don't have it
# pylint: disable=invalid-name
@dataclass(init=False)
class Update(Verb):
"""Update activity"""
to: List[str]
type: str = "Update"
def action(self, allow_external_connections=True):
"""update a model instance from the dataclass"""
if not self.object:
return
self.object.to_model(
allow_create=False, allow_external_connections=allow_external_connections
)
@dataclass(init=False)
class Undo(Verb):
"""Undo an activity"""
type: str = "Undo"
def action(self, allow_external_connections=True):
"""find and remove the activity object"""
if isinstance(self.object, str):
# it may be that something should be done with these, but idk what
# this seems just to be coming from pleroma
return
# this is so hacky but it does make it work....
# (because you Reject a request and Undo a follow
model = None
if self.object.type == "Follow":
model = apps.get_model("bookwyrm.UserFollows")
obj = self.object.to_model(
model=model,
save=False,
allow_create=False,
allow_external_connections=allow_external_connections,
)
if not obj:
# this could be a follow request not a follow proper
model = apps.get_model("bookwyrm.UserFollowRequest")
obj = self.object.to_model(
model=model,
save=False,
allow_create=False,
allow_external_connections=allow_external_connections,
)
else:
obj = self.object.to_model(
model=model,
save=False,
allow_create=False,
allow_external_connections=allow_external_connections,
)
if not obj:
# if we don't have the object, we can't undo it. happens a lot with boosts
return
obj.delete()
@dataclass(init=False)
class Follow(Verb):
"""Follow activity"""
object: str
type: str = "Follow"
def action(self, allow_external_connections=True):
"""relationship save"""
self.to_model(allow_external_connections=allow_external_connections)
@dataclass(init=False)
class Block(Verb):
"""Block activity"""
object: str
type: str = "Block"
def action(self, allow_external_connections=True):
"""relationship save"""
self.to_model(allow_external_connections=allow_external_connections)
@dataclass(init=False)
class Accept(Verb):
"""Accept activity"""
object: Follow
type: str = "Accept"
def action(self, allow_external_connections=True):
"""accept a request"""
obj = self.object.to_model(save=False, allow_create=True)
obj.accept()
@dataclass(init=False)
class Reject(Verb):
"""Reject activity"""
object: Follow
type: str = "Reject"
def action(self, allow_external_connections=True):
"""reject a follow request"""
obj = self.object.to_model(save=False, allow_create=False)
obj.reject()
@dataclass(init=False)
class Add(Verb):
"""Add activity"""
target: ActivityObject
object: CollectionItem
type: str = "Add"
def action(self, allow_external_connections=True):
"""figure out the target to assign the item to a collection"""
target = resolve_remote_id(self.target)
item = self.object.to_model(save=False)
setattr(item, item.collection_field, target)
item.save()
@dataclass(init=False)
class Remove(Add):
"""Remove activity"""
type: str = "Remove"
def action(self, allow_external_connections=True):
"""find and remove the activity object"""
obj = self.object.to_model(save=False, allow_create=False)
if obj:
obj.delete()
@dataclass(init=False)
class Like(Verb):
"""a user faving an object"""
object: str
type: str = "Like"
def action(self, allow_external_connections=True):
"""like"""
self.to_model(allow_external_connections=allow_external_connections)
# pylint: disable=invalid-name
@dataclass(init=False)
class Announce(Verb):
"""boosting a status"""
published: str
to: List[str] = field(default_factory=lambda: [])
cc: List[str] = field(default_factory=lambda: [])
object: str
type: str = "Announce"
def action(self, allow_external_connections=True):
"""boost"""
self.to_model(allow_external_connections=allow_external_connections)