foxhole/app/boxes.py
2023-03-20 17:54:54 +08:00

146 lines
3.8 KiB
Python

#!/usr/bin/env python3
from typing import Any
import uuid
from sqlalchemy.orm import session
from app import models
from app.database import AsyncSession
from app.models import InboxObject, now
from app.activitypub import ME
from app.activitypub import handle_visibility
from app.config import MANUALLY_APPROVES_FOLLOWERS
from app.config import BASE_URL
from app.models import Actor
from app.actor import fetch_actor
from app.actor import BaseActor
import app.activitypub as ap
from urllib.parse import urlparse
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from loguru import logger
from uuid import uuid4
def allocate_outbox_id() -> str:
return str(uuid.uuid4())
def build_object_id(id) -> str:
return f"{BASE_URL}/tail/{id}"
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,
)
await process_incoming(db_session, payload)
if db_session.add(incoming_activity):
return incoming_activity
await db_session.commit()
await db_session.refresh(incoming_activity)
return incoming_activity
async def process_incoming(
db_session: AsyncSession,
ap_object: dict,
) -> bool:
actor = await fetch_actor(ap_object["actor"], db_session)
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
if "Follow" == ap_object["type"]:
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):
return True
return False
return False
async def _handle_follow(
db_session : AsyncSession,
actor : Actor,
inbox_object : InboxObject,
) -> bool:
if ME["id"] != inbox_object.ap_object["object"]: #type: ignore
# await db_session.delete(ap_object)
logger.warning("no match follow object!" + inbox_object.ap_object["id"]) #type: ignore
return False
if MANUALLY_APPROVES_FOLLOWERS:
# TODO
return False
await _send_accept(db_session, actor, inbox_object)
return True
async def _send_accept(
db_session: AsyncSession,
actor : Actor,
inbox_object : InboxObject,
) -> None :
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!")
reply_id = allocate_outbox_id()
url = actor.inbox_url # type: ignore
out = {
"@context": ap.AS_CTX,
"id": build_object_id(reply_id),
"type": "Accept",
"actor": ME["id"],
"object": inbox_object.ap_object["id"], #type: ignore
}
#TODO outcoming
await ap.post(url, out) # type: ignore