foxhole/app/boxes.py

147 lines
3.8 KiB
Python
Raw Normal View History

2023-03-17 10:59:29 +01:00
#!/usr/bin/env python3
2023-03-18 13:02:38 +01:00
from typing import Any
import uuid
2023-03-20 10:54:54 +01:00
from sqlalchemy.orm import session
2023-03-17 10:59:29 +01:00
from app import models
from app.database import AsyncSession
2023-03-20 10:54:54 +01:00
from app.models import InboxObject, now
2023-03-17 18:46:31 +01:00
from app.activitypub import ME
2023-03-20 10:54:54 +01:00
from app.activitypub import handle_visibility
2023-03-17 18:46:31 +01:00
from app.config import MANUALLY_APPROVES_FOLLOWERS
from app.config import BASE_URL
2023-03-18 13:02:38 +01:00
from app.models import Actor
from app.actor import fetch_actor
2023-03-20 10:54:54 +01:00
from app.actor import BaseActor
2023-03-17 10:59:29 +01:00
2023-03-17 18:46:31 +01:00
import app.activitypub as ap
2023-03-18 13:02:38 +01:00
2023-03-20 10:54:54 +01:00
from urllib.parse import urlparse
2023-03-18 13:02:38 +01:00
from sqlalchemy import select
2023-03-20 10:54:54 +01:00
from sqlalchemy.exc import IntegrityError
2023-03-18 13:02:38 +01:00
from loguru import logger
from uuid import uuid4
2023-03-17 18:46:31 +01:00
def allocate_outbox_id() -> str:
return str(uuid.uuid4())
def build_object_id(id) -> str:
return f"{BASE_URL}/tail/{id}"
2023-03-17 10:59:29 +01:00
2023-03-20 10:54:54 +01:00
2023-03-17 10:59:29 +01:00
async def save_incoming(
db_session: AsyncSession,
payload: dict,
) -> models.IncomingActivity | None:
ap_id: str
if "@context" not in payload:
logger.warning(f"invalid object: {payload}")
return None
if "id" in payload:
ap_id = payload["id"]
else:
ap_id = str(uuid4())
incoming_activity = models.IncomingActivity(
ap_id=ap_id,
ap_object=payload,
)
2023-03-17 18:46:31 +01:00
await process_incoming(db_session, payload)
2023-03-18 09:12:44 +01:00
if db_session.add(incoming_activity):
return incoming_activity
2023-03-17 10:59:29 +01:00
await db_session.commit()
await db_session.refresh(incoming_activity)
return incoming_activity
2023-03-17 18:46:31 +01:00
async def process_incoming(
db_session: AsyncSession,
ap_object: dict,
) -> bool:
2023-03-18 13:02:38 +01:00
actor = await fetch_actor(ap_object["actor"], db_session)
2023-03-17 18:46:31 +01:00
2023-03-20 10:54:54 +01:00
def save_to_db(object) -> InboxObject:
inbox_object = models.InboxObject(
actor_id=actor.id,
server=urlparse(object["id"]).hostname,
is_hidden_from_stream=True,
ap_actor_id=actor.ap_id,
ap_type=object["type"],
ap_id=object["id"],
ap_published_at=now(),
ap_object=object,
visibility=handle_visibility(object),
activity_object_ap_id=object["actor"]
#TODO relates
)
return inbox_object
2023-03-17 18:46:31 +01:00
if "Follow" == ap_object["type"]:
2023-03-20 10:54:54 +01:00
inbox_object = save_to_db(ap_object)
db_session.add(inbox_object)
await db_session.flush()
await db_session.refresh(inbox_object)
if await _handle_follow(db_session, actor, inbox_object):
2023-03-18 15:55:49 +01:00
return True
return False
2023-03-18 09:12:44 +01:00
return False
2023-03-17 18:46:31 +01:00
async def _handle_follow(
2023-03-18 13:02:38 +01:00
db_session : AsyncSession,
2023-03-20 10:54:54 +01:00
actor : Actor,
inbox_object : InboxObject,
2023-03-18 09:12:44 +01:00
) -> bool:
2023-03-20 10:54:54 +01:00
if ME["id"] != inbox_object.ap_object["object"]: #type: ignore
2023-03-17 18:46:31 +01:00
# await db_session.delete(ap_object)
2023-03-20 10:54:54 +01:00
logger.warning("no match follow object!" + inbox_object.ap_object["id"]) #type: ignore
2023-03-18 09:12:44 +01:00
return False
2023-03-17 18:46:31 +01:00
if MANUALLY_APPROVES_FOLLOWERS:
# TODO
2023-03-18 09:12:44 +01:00
return False
2023-03-17 18:46:31 +01:00
2023-03-20 10:54:54 +01:00
await _send_accept(db_session, actor, inbox_object)
2023-03-18 09:12:44 +01:00
return True
2023-03-17 18:46:31 +01:00
async def _send_accept(
db_session: AsyncSession,
2023-03-20 10:54:54 +01:00
actor : Actor,
inbox_object : InboxObject,
2023-03-17 18:46:31 +01:00
) -> None :
2023-03-20 10:54:54 +01:00
actor = BaseActor(actor.ap_actor)
follower = models.Follower(
actor_id=inbox_object.actor_id,
inbox_object_id=inbox_object.id,
ap_actor_id=inbox_object.ap_object["actor"], #type: ignore
)
try:
db_session.add(follower)
await db_session.flush()
except IntegrityError:
await db_session.rollback()
logger.warning("existing follower in db!")
2023-03-17 18:46:31 +01:00
reply_id = allocate_outbox_id()
2023-03-18 04:57:20 +01:00
2023-03-20 10:54:54 +01:00
url = actor.inbox_url # type: ignore
2023-03-17 18:46:31 +01:00
out = {
"@context": ap.AS_CTX,
"id": build_object_id(reply_id),
"type": "Accept",
"actor": ME["id"],
2023-03-20 10:54:54 +01:00
"object": inbox_object.ap_object["id"], #type: ignore
2023-03-17 18:46:31 +01:00
}
2023-03-18 04:57:20 +01:00
#TODO outcoming
2023-03-20 10:54:54 +01:00
await ap.post(url, out) # type: ignore