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 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( async def fetch_actor(
db_session : AsyncSession, db_session : AsyncSession,
@ -51,15 +31,6 @@ async def fetch_actor(
exist_actor = await save_actor(ap_object, db_session) exist_actor = await save_actor(ap_object, db_session)
return exist_actor return exist_actor
else: 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 return exist_actor
async def save_actor( async def save_actor(
@ -89,3 +60,18 @@ def _handle (
handle = '@' + ap_object["preferredUsername"] + '@' + ap_id.hostname handle = '@' + ap_object["preferredUsername"] + '@' + ap_id.hostname
return handle 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 sqlalchemy.orm import session
from app import models from app import models
from app import ldsig
from app.database import AsyncSession from app.database import AsyncSession
from app.models import InboxObject, OutboxObject, now from app.models import InboxObject, OutboxObject, now
from app.activitypub import ME from app.activitypub import ME
from app.activitypub import handle_visibility from app.activitypub import handle_visibility
from app.config import MANUALLY_APPROVES_FOLLOWERS 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.models import Actor
from app.actor import fetch_actor from app.actor import fetch_actor
from app.httpsig import k
import app.activitypub as ap import app.activitypub as ap
@ -23,6 +25,7 @@ from sqlalchemy.orm import joinedload
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from loguru import logger from loguru import logger
from uuid import uuid4 from uuid import uuid4
from datetime import datetime
@ -133,6 +136,8 @@ async def process_incoming(
await db_session.flush() await db_session.flush()
await db_session.refresh(following) await db_session.refresh(following)
return True return True
# elif "Creat" == ap_object["type"]:
return False return False
@ -173,18 +178,21 @@ async def _send_accept(
await db_session.rollback() await db_session.rollback()
logger.warning("existing follower in db!") logger.warning("existing follower in db!")
reply_id = allocate_outbox_id() try:
reply_id = allocate_outbox_id()
url = actor.inbox_url # type: ignore url = actor.inbox_url # type: ignore
out = { out = {
"@context": ap.AS_CTX, "@context": ap.AS_CTX,
"id": build_object_id(reply_id), "id": build_object_id(reply_id),
"type": "Accept", "type": "Accept",
"actor": ME["id"], "actor": ME["id"],
"object": inbox_object.ap_object["id"], #type: ignore "object": inbox_object.ap_object["id"], #type: ignore
} }
#TODO outcoming #TODO outcoming
await ap.post(url, out) # type: ignore await ap.post(url, out) # type: ignore
except Exception as e:
logger.error(e)
async def _handle_undo( 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( async def save_to_inbox(
db_session : AsyncSession, db_session : AsyncSession,
inbox_id : str, inbox_id : str,

View file

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