social/barkshark_social/processors.py
2024-04-18 22:36:06 -04:00

133 lines
3.4 KiB
Python

from __future__ import annotations
import logging
import httpx
import traceback
from aputils import Message, ObjectType
from basgi import Request
from collections.abc import Awaitable, Callable
from . import TRANS
from .database.objects import Follow, User
ProcessHandler = Callable[[Request, Message, User], Awaitable[None]]
PROCESSORS: dict[ObjectType, ProcessHandler] = {}
def register(*objtypes: ObjectType) -> Callable[[ProcessHandler], ProcessHandler]:
def wrapper(func: ProcessHandler) -> ProcessHandler:
for objtype in objtypes:
PROCESSORS[objtype] = func
return func
return wrapper
@register(ObjectType.FOLLOW)
async def handle_follow(request: Request, message: Message, user: User) -> None:
if message.object_id != user.actor:
logging.warning(
"Follow object does not match actor: msg=%s, actor=%s",
message.object_id,
user.actor
)
with request.app.state.database.session(False) as s:
with s.transaction():
if (follow := s.get_follow(request.state.user, user)) is not None:
follow.followid = message.id
follow = s.update_row(follow)
else:
follow = s.insert_row(Follow.new_from_users(
s, request.state.user, user, message.id, not user.locked
))
if not follow.accepted:
return
msg = Message.new(ObjectType.ACCEPT if follow.accepted else ObjectType.REJECT, {
'id': f'https://{request.app.state.config.web_host}/activity/follow/{follow.id}',
'to': [request.state.user.actor],
'actor': user.actor,
'object': {
'id': follow.followid,
'type': 'Follow',
'object': user.actor,
'actor': request.state.user.actor
}
})
await request.app.client.post(request.state.user.inbox, msg, signer = user.signer)
@register(ObjectType.UNDO)
async def handle_undo(request: Request, message: Message, user: User) -> None:
if isinstance(message.object, str):
obj = await request.app.client.fetch_json(
message.object,
Message,
signer = user.signer
)
else:
obj = Message.parse(message.object)
if obj.type not in ObjectType.Actor:
print(repr(obj.type))
return
if obj.object_id != user.actor:
logging.warning(
"Follow object does not match actor: msg=%s, actor=%s",
message.object_id,
user.actor
)
with request.app.state.database.session(False) as s:
if (follow := s.get_follow_by_id(obj.id)) is None:
logging.warning("Could not find follow with id: %s", obj.id)
return
if follow.sourceid != request.state.user.id or follow.targetid != user.id:
logging.warning(TRANS.fetch(
"error", "mismatched-ids",
fsid = follow.sourceid,
ftid = follow.targetid,
sid = request.state.user.id,
tid = user.id
))
return
with s.transaction():
s.delete_row(follow)
async def process_message(request: Request, message: Message, user: User) -> None:
print(f"process_message: {user.handle}, message:", message.to_json(4), flush = True)
if request.state.user.actor != message.actor_id:
logging.warning(
"Message actor and signature keyid do not match: msg=%s, actor=%s",
message.actor_id,
request.state.user.actor
)
try:
handler = PROCESSORS[message.type]
await handler(request, message, user)
except httpx.HTTPStatusError as error:
logging.error("Error when sending request: %i", error.response.status_code)
logging.error(error.response.read())
except KeyError:
logging.debug("Unhandled message type: %s", message.type)
except Exception:
traceback.print_exc()