refactor/actor model
This commit is contained in:
parent
43a19e81d4
commit
d321d07a49
3 changed files with 142 additions and 42 deletions
44
app/actor.py
44
app/actor.py
|
@ -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
|
||||
|
|
138
app/boxes.py
138
app/boxes.py
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue