refactor/actor model

This commit is contained in:
SouthFox 2023-04-02 00:13:16 +08:00
parent 43a19e81d4
commit d321d07a49
3 changed files with 142 additions and 42 deletions

View file

@ -13,26 +13,6 @@ if typing.TYPE_CHECKING:
import app.activitypub as ap
class BaseActor:
def __init__(self, ap_actor: ap.RawObject) -> None:
if (ap_type := ap_actor.get("type")) not in ap.ACTOR_TYPES:
raise ValueError(f"Unexpected actor type: {ap_type}")
self._ap_actor = ap_actor
self._ap_type : str = ap_type # type: ignore
@property
def ap_actor(self) -> ap.RawObject:
return self._ap_actor
@property
def inbox_url(self) -> str:
return self.ap_actor["inbox"]
@property
def ap_type(self) -> str:
return self._ap_type
async def fetch_actor(
db_session : AsyncSession,
@ -51,15 +31,6 @@ async def fetch_actor(
exist_actor = await save_actor(ap_object, db_session)
return exist_actor
else:
try:
_actor = await ap.fetch(actor_id)
exist_actor = await save_actor(_actor, db_session)
except json.JSONDecodeError:
raise ValueError
except KeyError:
logger.warning("actor gone? ")
raise KeyError
return exist_actor
async def save_actor(
@ -89,3 +60,18 @@ def _handle (
handle = '@' + ap_object["preferredUsername"] + '@' + ap_id.hostname
return handle
async def get_public_key(
db_session: AsyncSession,
key_id: str
) -> str:
existing_actor = (
await db_session.scalars(
select(models.Actor).where(models.Actor.ap_id == key_id.split("#")[0])
)
).one_or_none()
public_key = existing_actor.ap_object["publicKey"]["publicKeyPem"]
return public_key

View file

@ -5,14 +5,16 @@ import uuid
from sqlalchemy.orm import session
from app import models
from app import ldsig
from app.database import AsyncSession
from app.models import InboxObject, OutboxObject, 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.config import BASE_URL, ID
from app.models import Actor
from app.actor import fetch_actor
from app.httpsig import k
import app.activitypub as ap
@ -23,6 +25,7 @@ from sqlalchemy.orm import joinedload
from sqlalchemy.exc import IntegrityError
from loguru import logger
from uuid import uuid4
from datetime import datetime
@ -133,6 +136,8 @@ async def process_incoming(
await db_session.flush()
await db_session.refresh(following)
return True
# elif "Creat" == ap_object["type"]:
return False
@ -173,18 +178,21 @@ async def _send_accept(
await db_session.rollback()
logger.warning("existing follower in db!")
reply_id = allocate_outbox_id()
try:
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
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
except Exception as e:
logger.error(e)
async def _handle_undo(
@ -260,6 +268,112 @@ async def _send_follow(
)
async def _send_create(
db_session: AsyncSession,
ap_type: str,
content: str,
visibility: ap.VisibilityEnum,
published: str | None = None,
) -> bool:
object_id = build_object_id(allocate_outbox_id())
if not published:
published = now().replace(microsecond=0).isoformat().replace("+00:00", "Z")
to = []
cc = []
if visibility == ap.VisibilityEnum.PUBLIC:
to = [ap.AS_PUBLIC]
cc = [f"{BASE_URL}/followers"]
else:
raise ValueError(f"Unsupport visibility {visibility}")
ap_object = {
"@context": ap.AS_EXTENDED_CTX,
"type": ap_type,
"id": object_id,
"attributedTo": ID,
"content": content,
"to": to,
"cc": cc,
"published": published,
# "context": context,
# "conversation": context,
"url": object_id,
"tag": [],
"summary": None,
"inReplyTo": None,
"sensitive": False,
"attachment": [],
}
outbox_object = await save_to_outbox(
db_session,
object_id,
ap_object,
)
recipients = await _compute_recipients(db_session, ap_object)
ap_object = ap.wrap_ap_object(ap_object)
if ap_object["type"] == "Create":
if ap.VisibilityEnum.PUBLIC == outbox_object.visibility:
ldsig.generate_signature(ap_object, k)
for r in recipients:
await ap.post(
r,
ap_object,
)
return True
async def _compute_recipients(
db_session: AsyncSession,
ap_object: dict,
) -> set[str]:
async def process_collection(
db_session,
url) -> list[Actor]:
if url == BASE_URL + "/followers":
followers = (
(
await db_session.scalars(
select(models.Follower).options(
joinedload(models.Follower.actor)
)
)
)
.unique()
.all()
)
else:
raise ValueError(f"{url}) not supported")
return [follower.actor for follower in followers]
_recipients = []
for field in ["to", "cc", "bcc", "bto"]:
if field in ap_object:
_recipients.extend(ap_object[field])
recipients = set()
logger.info(f"{_recipients}")
for r in _recipients:
if r in [ap.AS_PUBLIC, ID]:
continue
if r.startswith(BASE_URL):
for actor in await process_collection(db_session, r):
recipients.add(actor.share_inbox_url)
continue
return recipients
async def save_to_inbox(
db_session : AsyncSession,
inbox_id : str,

View file

@ -7,7 +7,7 @@ from typing import Union
from app import activitypub as ap
from app.database import Base
from app.database import metadata_obj
from app.actor import BaseActor
from app.activitypub import BaseActor
from sqlalchemy import Column
from sqlalchemy import Boolean