Compare commits

...

2 commits

Author SHA1 Message Date
209817451c WIP/some WIP 2023-03-20 17:54:54 +08:00
1233ca6050 db/update models 2023-03-20 16:03:01 +08:00
5 changed files with 90 additions and 16 deletions

View file

@ -31,6 +31,7 @@ def upgrade() -> None:
sa.Column('ap_context', sa.String(), nullable=True), sa.Column('ap_context', sa.String(), nullable=True),
sa.Column('ap_published_at', sa.DateTime(timezone=True), nullable=False), sa.Column('ap_published_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('ap_object', sa.JSON(), nullable=False), sa.Column('ap_object', sa.JSON(), nullable=False),
sa.Column('activity_object_ap_id', sa.String(), nullable=True),
sa.Column('visibility', sa.Enum('PUBLIC', 'UNLISTED', 'FOLLOWERS_ONLY', 'DIRECT', name='visibilityenum'), nullable=False), sa.Column('visibility', sa.Enum('PUBLIC', 'UNLISTED', 'FOLLOWERS_ONLY', 'DIRECT', name='visibilityenum'), nullable=False),
sa.Column('relates_to_inbox_object_id', sa.Integer(), nullable=True), sa.Column('relates_to_inbox_object_id', sa.Integer(), nullable=True),
sa.Column('relates_to_outbox_object_id', sa.Integer(), nullable=True), sa.Column('relates_to_outbox_object_id', sa.Integer(), nullable=True),
@ -41,6 +42,7 @@ def upgrade() -> None:
) )
op.create_index(op.f('ix_inbox_ap_id'), 'inbox', ['ap_id'], unique=True) op.create_index(op.f('ix_inbox_ap_id'), 'inbox', ['ap_id'], unique=True)
op.create_index(op.f('ix_inbox_ap_type'), 'inbox', ['ap_type'], unique=False) op.create_index(op.f('ix_inbox_ap_type'), 'inbox', ['ap_type'], unique=False)
op.create_index(op.f('ix_inbox_activity_object_ap_id'), 'inbox', ['activity_object_ap_id'], unique=False)
op.create_index(op.f('ix_inbox_id'), 'inbox', ['id'], unique=False) op.create_index(op.f('ix_inbox_id'), 'inbox', ['id'], unique=False)
op.create_table('outbox', op.create_table('outbox',
sa.Column('id', sa.Integer(), nullable=False), sa.Column('id', sa.Integer(), nullable=False),

View file

@ -81,6 +81,20 @@ class VisibilityEnum(str, enum.Enum):
FOLLOWERS_ONLY = "followers-only" FOLLOWERS_ONLY = "followers-only"
DIRECT = "direct" DIRECT = "direct"
def handle_visibility(
ap_object: dict
) -> VisibilityEnum:
to = ap_object.get("to", [])
cc = ap_object.get("cc", [])
if AS_PUBLIC in to:
return VisibilityEnum.PUBLIC
elif AS_PUBLIC in cc:
return VisibilityEnum.UNLISTED
else:
return VisibilityEnum.DIRECT
async def post( async def post(
url: str, url: str,
payload : dict, payload : dict,
@ -100,6 +114,7 @@ async def post(
return resp return resp
async def fetch( async def fetch(
url: str, url: str,
) -> dict: ) -> dict:

View file

@ -7,6 +7,22 @@ from urllib.parse import urlparse
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 = ap_type
@property
def ap_actor(self) -> ap.RawObject:
return self._ap_actor
@property
def inbox_url(self) -> str:
return self.ap_actor["inbox"]
async def fetch_actor( async def fetch_actor(
actor_id : str, actor_id : str,
db_session : AsyncSession, db_session : AsyncSession,

View file

@ -2,18 +2,24 @@
from typing import Any from typing import Any
import uuid import uuid
from sqlalchemy.orm import session
from app import models from app import models
from app.database import AsyncSession from app.database import AsyncSession
from app.models import InboxObject, now
from app.activitypub import ME from app.activitypub import ME
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
from app.models import Actor from app.models import Actor
from app.actor import fetch_actor from app.actor import fetch_actor
from app.actor import BaseActor
import app.activitypub as ap import app.activitypub as ap
from urllib.parse import urlparse
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from loguru import logger from loguru import logger
from uuid import uuid4 from uuid import uuid4
@ -26,6 +32,7 @@ def allocate_outbox_id() -> str:
def build_object_id(id) -> str: def build_object_id(id) -> str:
return f"{BASE_URL}/tail/{id}" return f"{BASE_URL}/tail/{id}"
async def save_incoming( async def save_incoming(
db_session: AsyncSession, db_session: AsyncSession,
payload: dict, payload: dict,
@ -59,8 +66,29 @@ async def process_incoming(
) -> bool: ) -> bool:
actor = await fetch_actor(ap_object["actor"], db_session) 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"]: if "Follow" == ap_object["type"]:
if await _handle_follow(db_session, actor.ap_actor["inbox"], ap_object): 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 True
return False return False
@ -69,37 +97,50 @@ async def process_incoming(
async def _handle_follow( async def _handle_follow(
db_session : AsyncSession, db_session : AsyncSession,
inbox_url : str | Any, actor : Actor,
ap_object : dict, inbox_object : InboxObject,
) -> bool: ) -> bool:
if ME["id"] != ap_object["object"]: if ME["id"] != inbox_object.ap_object["object"]: #type: ignore
# await db_session.delete(ap_object) # await db_session.delete(ap_object)
logger.warning("no match follow object!" + ap_object["id"]) logger.warning("no match follow object!" + inbox_object.ap_object["id"]) #type: ignore
return False return False
if MANUALLY_APPROVES_FOLLOWERS: if MANUALLY_APPROVES_FOLLOWERS:
# TODO # TODO
return False return False
await _send_accept(db_session, inbox_url, ap_object) await _send_accept(db_session, actor, inbox_object)
return True return True
async def _send_accept( async def _send_accept(
db_session: AsyncSession, db_session: AsyncSession,
inbox_url : str | Any, actor : Actor,
ap_object : dict, inbox_object : InboxObject,
) -> None : ) -> 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() reply_id = allocate_outbox_id()
url = inbox_url 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": ap_object["id"], "object": inbox_object.ap_object["id"], #type: ignore
} }
#TODO outcoming #TODO outcoming
await ap.post(url, out) await ap.post(url, out) # type: ignore

View file

@ -61,6 +61,7 @@ class InboxObject(Base):
ap_context = Column(String, nullable=True) ap_context = Column(String, nullable=True)
ap_published_at = Column(DateTime(timezone=True), nullable=False) ap_published_at = Column(DateTime(timezone=True), nullable=False)
ap_object: Mapped[dict[str, Any]] = Column(JSON, nullable=False) # type: ignore ap_object: Mapped[dict[str, Any]] = Column(JSON, nullable=False) # type: ignore
activity_object_ap_id = Column(String, nullable=True, index=True)
visibility = Column(Enum(ap.VisibilityEnum), nullable=False) visibility = Column(Enum(ap.VisibilityEnum), nullable=False)
@ -71,7 +72,7 @@ class InboxObject(Base):
) )
relates_to_inbox_object: Mapped[Optional["InboxObject"]] = relationship( relates_to_inbox_object: Mapped[Optional["InboxObject"]] = relationship(
"InboxObject", "InboxObject",
foreign_keys=relates_to_inbox_object_id, foreign_keys=[relates_to_inbox_object_id],
remote_side=id, remote_side=id,
uselist=False, uselist=False,
) )
@ -82,8 +83,7 @@ class InboxObject(Base):
) )
relates_to_outbox_object: Mapped[Optional["OutboxObject"]] = relationship( relates_to_outbox_object: Mapped[Optional["OutboxObject"]] = relationship(
"OutboxObject", "OutboxObject",
foreign_keys=[relates_to_outbox_object_id], foreign_keys=relates_to_outbox_object_id,
remote_side=id,
uselist=False, uselist=False,
) )
@ -117,7 +117,7 @@ class OutboxObject(Base):
) )
relates_to_inbox_object: Mapped[Optional["InboxObject"]] = relationship( relates_to_inbox_object: Mapped[Optional["InboxObject"]] = relationship(
"InboxObject", "InboxObject",
foreign_keys=[relates_to_inbox_object_id], foreign_keys=relates_to_inbox_object_id,
uselist=False, uselist=False,
) )
relates_to_outbox_object_id = Column( relates_to_outbox_object_id = Column(