Compare commits
2 commits
b7ecc4728b
...
209817451c
Author | SHA1 | Date | |
---|---|---|---|
209817451c | |||
1233ca6050 |
5 changed files with 90 additions and 16 deletions
|
@ -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),
|
||||||
|
|
|
@ -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:
|
||||||
|
|
16
app/actor.py
16
app/actor.py
|
@ -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,
|
||||||
|
|
65
app/boxes.py
65
app/boxes.py
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Reference in a new issue