685 lines
23 KiB
Python
685 lines
23 KiB
Python
"""Actions related to the AP inbox/outbox."""
|
|
import uuid
|
|
from urllib.parse import urlparse
|
|
|
|
import httpx
|
|
from dateutil.parser import isoparse
|
|
from loguru import logger
|
|
from sqlalchemy.exc import IntegrityError
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy.orm import joinedload
|
|
|
|
from app import activitypub as ap
|
|
from app import config
|
|
from app import models
|
|
from app.actor import LOCAL_ACTOR
|
|
from app.actor import RemoteActor
|
|
from app.actor import fetch_actor
|
|
from app.actor import save_actor
|
|
from app.ap_object import RemoteObject
|
|
from app.config import BASE_URL
|
|
from app.config import ID
|
|
from app.database import now
|
|
from app.process_outgoing_activities import new_outgoing_activity
|
|
from app.source import markdownify
|
|
|
|
|
|
def allocate_outbox_id() -> str:
|
|
return uuid.uuid4().hex
|
|
|
|
|
|
def outbox_object_id(outbox_id) -> str:
|
|
return f"{BASE_URL}/o/{outbox_id}"
|
|
|
|
|
|
def save_outbox_object(
|
|
db: Session,
|
|
public_id: str,
|
|
raw_object: ap.RawObject,
|
|
relates_to_inbox_object_id: int | None = None,
|
|
relates_to_outbox_object_id: int | None = None,
|
|
source: str | None = None,
|
|
) -> models.OutboxObject:
|
|
ra = RemoteObject(raw_object)
|
|
|
|
outbox_object = models.OutboxObject(
|
|
public_id=public_id,
|
|
ap_type=ra.ap_type,
|
|
ap_id=ra.ap_id,
|
|
ap_context=ra.context,
|
|
ap_object=ra.ap_object,
|
|
visibility=ra.visibility,
|
|
og_meta=ra.og_meta,
|
|
relates_to_inbox_object_id=relates_to_inbox_object_id,
|
|
relates_to_outbox_object_id=relates_to_outbox_object_id,
|
|
activity_object_ap_id=ra.activity_object_ap_id,
|
|
is_hidden_from_homepage=True if ra.in_reply_to else False,
|
|
)
|
|
db.add(outbox_object)
|
|
db.commit()
|
|
db.refresh(outbox_object)
|
|
|
|
return outbox_object
|
|
|
|
|
|
def send_like(db: Session, ap_object_id: str) -> None:
|
|
inbox_object = get_inbox_object_by_ap_id(db, ap_object_id)
|
|
if not inbox_object:
|
|
raise ValueError(f"{ap_object_id} not found in the inbox")
|
|
|
|
like_id = allocate_outbox_id()
|
|
like = {
|
|
"@context": ap.AS_CTX,
|
|
"id": outbox_object_id(like_id),
|
|
"type": "Like",
|
|
"actor": ID,
|
|
"object": ap_object_id,
|
|
}
|
|
outbox_object = save_outbox_object(
|
|
db, like_id, like, relates_to_inbox_object_id=inbox_object.id
|
|
)
|
|
if not outbox_object.id:
|
|
raise ValueError("Should never happen")
|
|
|
|
inbox_object.liked_via_outbox_object_ap_id = outbox_object.ap_id
|
|
db.commit()
|
|
|
|
new_outgoing_activity(db, inbox_object.actor.inbox_url, outbox_object.id)
|
|
|
|
|
|
def send_announce(db: Session, ap_object_id: str) -> None:
|
|
inbox_object = get_inbox_object_by_ap_id(db, ap_object_id)
|
|
if not inbox_object:
|
|
raise ValueError(f"{ap_object_id} not found in the inbox")
|
|
|
|
announce_id = allocate_outbox_id()
|
|
announce = {
|
|
"@context": ap.AS_CTX,
|
|
"id": outbox_object_id(announce_id),
|
|
"type": "Announce",
|
|
"actor": ID,
|
|
"object": ap_object_id,
|
|
"to": [ap.AS_PUBLIC],
|
|
"cc": [
|
|
f"{BASE_URL}/followers",
|
|
inbox_object.ap_actor_id,
|
|
],
|
|
}
|
|
outbox_object = save_outbox_object(
|
|
db, announce_id, announce, relates_to_inbox_object_id=inbox_object.id
|
|
)
|
|
if not outbox_object.id:
|
|
raise ValueError("Should never happen")
|
|
|
|
inbox_object.announced_via_outbox_object_ap_id = outbox_object.ap_id
|
|
db.commit()
|
|
|
|
recipients = _compute_recipients(db, announce)
|
|
for rcp in recipients:
|
|
new_outgoing_activity(db, rcp, outbox_object.id)
|
|
|
|
|
|
def send_follow(db: Session, ap_actor_id: str) -> None:
|
|
actor = fetch_actor(db, ap_actor_id)
|
|
|
|
follow_id = allocate_outbox_id()
|
|
follow = {
|
|
"@context": ap.AS_CTX,
|
|
"id": outbox_object_id(follow_id),
|
|
"type": "Follow",
|
|
"actor": ID,
|
|
"object": ap_actor_id,
|
|
}
|
|
|
|
outbox_object = save_outbox_object(db, follow_id, follow)
|
|
if not outbox_object.id:
|
|
raise ValueError("Should never happen")
|
|
|
|
new_outgoing_activity(db, actor.inbox_url, outbox_object.id)
|
|
|
|
|
|
def send_undo(db: Session, ap_object_id: str) -> None:
|
|
outbox_object_to_undo = get_outbox_object_by_ap_id(db, ap_object_id)
|
|
if not outbox_object_to_undo:
|
|
raise ValueError(f"{ap_object_id} not found in the outbox")
|
|
|
|
if outbox_object_to_undo.ap_type not in ["Follow", "Like", "Announce"]:
|
|
raise ValueError(
|
|
f"Cannot build Undo for {outbox_object_to_undo.ap_type} activity"
|
|
)
|
|
|
|
undo_id = allocate_outbox_id()
|
|
undo = {
|
|
"@context": ap.AS_CTX,
|
|
"id": outbox_object_id(undo_id),
|
|
"type": "Undo",
|
|
"actor": ID,
|
|
"object": ap.remove_context(outbox_object_to_undo.ap_object),
|
|
}
|
|
|
|
outbox_object = save_outbox_object(
|
|
db,
|
|
undo_id,
|
|
undo,
|
|
relates_to_outbox_object_id=outbox_object_to_undo.id,
|
|
)
|
|
if not outbox_object.id:
|
|
raise ValueError("Should never happen")
|
|
|
|
outbox_object_to_undo.undone_by_outbox_object_id = outbox_object.id
|
|
|
|
if outbox_object_to_undo.ap_type == "Follow":
|
|
if not outbox_object_to_undo.activity_object_ap_id:
|
|
raise ValueError("Should never happen")
|
|
followed_actor = fetch_actor(db, outbox_object_to_undo.activity_object_ap_id)
|
|
new_outgoing_activity(
|
|
db,
|
|
followed_actor.inbox_url,
|
|
outbox_object.id,
|
|
)
|
|
# Also remove the follow from the following collection
|
|
db.query(models.Following).filter(
|
|
models.Following.ap_actor_id == followed_actor.ap_id
|
|
).delete()
|
|
db.commit()
|
|
elif outbox_object_to_undo.ap_type == "Like":
|
|
liked_object_ap_id = outbox_object_to_undo.activity_object_ap_id
|
|
if not liked_object_ap_id:
|
|
raise ValueError("Should never happen")
|
|
liked_object = get_inbox_object_by_ap_id(db, liked_object_ap_id)
|
|
if not liked_object:
|
|
raise ValueError(f"Cannot find liked object {liked_object_ap_id}")
|
|
liked_object.liked_via_outbox_object_ap_id = None
|
|
|
|
# Send the Undo to the liked object's actor
|
|
new_outgoing_activity(
|
|
db,
|
|
liked_object.actor.inbox_url, # type: ignore
|
|
outbox_object.id,
|
|
)
|
|
elif outbox_object_to_undo.ap_type == "Announce":
|
|
announced_object_ap_id = outbox_object_to_undo.activity_object_ap_id
|
|
if not announced_object_ap_id:
|
|
raise ValueError("Should never happen")
|
|
announced_object = get_inbox_object_by_ap_id(db, announced_object_ap_id)
|
|
if not announced_object:
|
|
raise ValueError(f"Cannot find announced object {announced_object_ap_id}")
|
|
announced_object.announced_via_outbox_object_ap_id = None
|
|
|
|
# Send the Undo to the original recipients
|
|
recipients = _compute_recipients(db, outbox_object.ap_object)
|
|
for rcp in recipients:
|
|
new_outgoing_activity(db, rcp, outbox_object.id)
|
|
else:
|
|
raise ValueError("Should never happen")
|
|
|
|
|
|
def send_create(db: Session, source: str) -> str:
|
|
note_id = allocate_outbox_id()
|
|
published = now().replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
context = f"{ID}/contexts/" + uuid.uuid4().hex
|
|
content, tags = markdownify(db, source)
|
|
note = {
|
|
"@context": ap.AS_CTX,
|
|
"type": "Note",
|
|
"id": outbox_object_id(note_id),
|
|
"attributedTo": ID,
|
|
"content": content,
|
|
"to": [ap.AS_PUBLIC],
|
|
"cc": [f"{BASE_URL}/followers"],
|
|
"published": published,
|
|
"context": context,
|
|
"conversation": context,
|
|
"url": outbox_object_id(note_id),
|
|
"tag": tags,
|
|
"summary": None,
|
|
"inReplyTo": None,
|
|
"sensitive": False,
|
|
}
|
|
outbox_object = save_outbox_object(db, note_id, note, source=source)
|
|
if not outbox_object.id:
|
|
raise ValueError("Should never happen")
|
|
|
|
for tag in tags:
|
|
if tag["type"] == "Hashtag":
|
|
tagged_object = models.TaggedOutboxObject(
|
|
tag=tag["name"][1:],
|
|
outbox_object_id=outbox_object.id,
|
|
)
|
|
db.add(tagged_object)
|
|
db.commit()
|
|
|
|
recipients = _compute_recipients(db, note)
|
|
for rcp in recipients:
|
|
new_outgoing_activity(db, rcp, outbox_object.id)
|
|
|
|
return note_id
|
|
|
|
|
|
def _compute_recipients(db: Session, ap_object: ap.RawObject) -> set[str]:
|
|
_recipients = []
|
|
for field in ["to", "cc", "bto", "bcc"]:
|
|
if field in ap_object:
|
|
_recipients.extend(ap.as_list(ap_object[field]))
|
|
|
|
recipients = set()
|
|
for r in _recipients:
|
|
if r in [ap.AS_PUBLIC, ID]:
|
|
continue
|
|
|
|
# If we got a local collection, assume it's a collection of actors
|
|
if r.startswith(BASE_URL):
|
|
for raw_actor in fetch_collection(db, r):
|
|
actor = RemoteActor(raw_actor)
|
|
recipients.add(actor.shared_inbox_url or actor.inbox_url)
|
|
|
|
continue
|
|
|
|
# Is it a known actor?
|
|
known_actor = (
|
|
db.query(models.Actor).filter(models.Actor.ap_id == r).one_or_none()
|
|
)
|
|
if known_actor:
|
|
recipients.add(known_actor.shared_inbox_url or actor.inbox_url)
|
|
continue
|
|
|
|
# Fetch the object
|
|
raw_object = ap.fetch(r)
|
|
if raw_object.get("type") in ap.ACTOR_TYPES:
|
|
saved_actor = save_actor(db, raw_object)
|
|
recipients.add(saved_actor.shared_inbox_url or saved_actor.inbox_url)
|
|
else:
|
|
# Assume it's a collection of actors
|
|
for raw_actor in ap.parse_collection(payload=raw_object):
|
|
actor = RemoteActor(raw_actor)
|
|
recipients.add(actor.shared_inbox_url or actor.inbox_url)
|
|
|
|
return recipients
|
|
|
|
|
|
def get_inbox_object_by_ap_id(db: Session, ap_id: str) -> models.InboxObject | None:
|
|
return (
|
|
db.query(models.InboxObject)
|
|
.filter(models.InboxObject.ap_id == ap_id)
|
|
.one_or_none()
|
|
)
|
|
|
|
|
|
def get_outbox_object_by_ap_id(db: Session, ap_id: str) -> models.OutboxObject | None:
|
|
return (
|
|
db.query(models.OutboxObject)
|
|
.filter(models.OutboxObject.ap_id == ap_id)
|
|
.one_or_none()
|
|
)
|
|
|
|
|
|
def _handle_delete_activity(
|
|
db: Session,
|
|
from_actor: models.Actor,
|
|
ap_object_to_delete: models.InboxObject,
|
|
) -> None:
|
|
if from_actor.ap_id != ap_object_to_delete.actor.ap_id:
|
|
logger.warning(
|
|
"Actor mismatch between the activity and the object: "
|
|
f"{from_actor.ap_id}/{ap_object_to_delete.actor.ap_id}"
|
|
)
|
|
return
|
|
|
|
# TODO(ts): do we need to delete related activities? should we keep
|
|
# bookmarked objects with a deleted flag?
|
|
logger.info(f"Deleting {ap_object_to_delete.ap_type}/{ap_object_to_delete.ap_id}")
|
|
db.delete(ap_object_to_delete)
|
|
db.flush()
|
|
|
|
|
|
def _handle_follow_follow_activity(
|
|
db: Session,
|
|
from_actor: models.Actor,
|
|
inbox_object: models.InboxObject,
|
|
) -> None:
|
|
follower = models.Follower(
|
|
actor_id=from_actor.id,
|
|
inbox_object_id=inbox_object.id,
|
|
ap_actor_id=from_actor.ap_id,
|
|
)
|
|
try:
|
|
db.add(follower)
|
|
db.flush()
|
|
except IntegrityError:
|
|
pass # TODO update the existing followe
|
|
|
|
# Reply with an Accept
|
|
reply_id = allocate_outbox_id()
|
|
reply = {
|
|
"@context": ap.AS_CTX,
|
|
"id": outbox_object_id(reply_id),
|
|
"type": "Accept",
|
|
"actor": ID,
|
|
"object": inbox_object.ap_id,
|
|
}
|
|
outbox_activity = save_outbox_object(db, reply_id, reply)
|
|
if not outbox_activity.id:
|
|
raise ValueError("Should never happen")
|
|
new_outgoing_activity(db, from_actor.inbox_url, outbox_activity.id)
|
|
|
|
notif = models.Notification(
|
|
notification_type=models.NotificationType.NEW_FOLLOWER,
|
|
actor_id=from_actor.id,
|
|
)
|
|
db.add(notif)
|
|
|
|
|
|
def _handle_undo_activity(
|
|
db: Session,
|
|
from_actor: models.Actor,
|
|
undo_activity: models.InboxObject,
|
|
ap_activity_to_undo: models.InboxObject,
|
|
) -> None:
|
|
if from_actor.ap_id != ap_activity_to_undo.actor.ap_id:
|
|
logger.warning(
|
|
"Actor mismatch between the activity and the object: "
|
|
f"{from_actor.ap_id}/{ap_activity_to_undo.actor.ap_id}"
|
|
)
|
|
return
|
|
|
|
ap_activity_to_undo.undone_by_inbox_object_id = undo_activity.id
|
|
|
|
if ap_activity_to_undo.ap_type == "Follow":
|
|
logger.info(f"Undo follow from {from_actor.ap_id}")
|
|
db.query(models.Follower).filter(
|
|
models.Follower.inbox_object_id == ap_activity_to_undo.id
|
|
).delete()
|
|
notif = models.Notification(
|
|
notification_type=models.NotificationType.UNFOLLOW,
|
|
actor_id=from_actor.id,
|
|
)
|
|
db.add(notif)
|
|
|
|
elif ap_activity_to_undo.ap_type == "Like":
|
|
if not ap_activity_to_undo.activity_object_ap_id:
|
|
raise ValueError("Like without object")
|
|
liked_obj = get_outbox_object_by_ap_id(
|
|
db,
|
|
ap_activity_to_undo.activity_object_ap_id,
|
|
)
|
|
if not liked_obj:
|
|
logger.warning(
|
|
"Cannot find liked object: "
|
|
f"{ap_activity_to_undo.activity_object_ap_id}"
|
|
)
|
|
return
|
|
|
|
liked_obj.likes_count = models.OutboxObject.likes_count - 1
|
|
notif = models.Notification(
|
|
notification_type=models.NotificationType.UNDO_LIKE,
|
|
actor_id=from_actor.id,
|
|
outbox_object_id=liked_obj.id,
|
|
inbox_object_id=ap_activity_to_undo.id,
|
|
)
|
|
db.add(notif)
|
|
|
|
elif ap_activity_to_undo.ap_type == "Announce":
|
|
if not ap_activity_to_undo.activity_object_ap_id:
|
|
raise ValueError("Announce witout object")
|
|
announced_obj_ap_id = ap_activity_to_undo.activity_object_ap_id
|
|
logger.info(
|
|
f"Undo for announce {ap_activity_to_undo.ap_id}/{announced_obj_ap_id}"
|
|
)
|
|
if announced_obj_ap_id.startswith(BASE_URL):
|
|
announced_obj_from_outbox = get_outbox_object_by_ap_id(
|
|
db, announced_obj_ap_id
|
|
)
|
|
if announced_obj_from_outbox:
|
|
logger.info("Found in the oubox")
|
|
announced_obj_from_outbox.announces_count = (
|
|
models.OutboxObject.announces_count - 1
|
|
)
|
|
notif = models.Notification(
|
|
notification_type=models.NotificationType.UNDO_ANNOUNCE,
|
|
actor_id=from_actor.id,
|
|
outbox_object_id=announced_obj_from_outbox.id,
|
|
inbox_object_id=ap_activity_to_undo.id,
|
|
)
|
|
db.add(notif)
|
|
|
|
# FIXME(ts): what to do with ap_activity_to_undo? flag? delete?
|
|
else:
|
|
logger.warning(f"Don't know how to undo {ap_activity_to_undo.ap_type} activity")
|
|
|
|
# commit will be perfomed in save_to_inbox
|
|
|
|
|
|
def _handle_create_activity(
|
|
db: Session,
|
|
from_actor: models.Actor,
|
|
created_object: models.InboxObject,
|
|
) -> None:
|
|
logger.info("Processing Create activity")
|
|
tags = created_object.ap_object.get("tag")
|
|
|
|
if not tags:
|
|
logger.info("No tags to process")
|
|
return None
|
|
|
|
if not isinstance(tags, list):
|
|
logger.info(f"Invalid tags: {tags}")
|
|
return None
|
|
|
|
for tag in tags:
|
|
if tag.get("name") == LOCAL_ACTOR.handle or tag.get("href") == LOCAL_ACTOR.url:
|
|
notif = models.Notification(
|
|
notification_type=models.NotificationType.MENTION,
|
|
actor_id=from_actor.id,
|
|
inbox_object_id=created_object.id,
|
|
)
|
|
db.add(notif)
|
|
|
|
|
|
def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
|
|
try:
|
|
actor = fetch_actor(db, raw_object["actor"])
|
|
except httpx.HTTPStatusError:
|
|
logger.exception("Failed to fetch actor")
|
|
# XXX: Delete 410 when we never seen the actor
|
|
return
|
|
|
|
ap_published_at = now()
|
|
if "published" in raw_object:
|
|
ap_published_at = isoparse(raw_object["published"])
|
|
|
|
ra = RemoteObject(ap.unwrap_activity(raw_object), actor=actor)
|
|
relates_to_inbox_object: models.InboxObject | None = None
|
|
relates_to_outbox_object: models.OutboxObject | None = None
|
|
if ra.activity_object_ap_id:
|
|
if ra.activity_object_ap_id.startswith(BASE_URL):
|
|
relates_to_outbox_object = get_outbox_object_by_ap_id(
|
|
db,
|
|
ra.activity_object_ap_id,
|
|
)
|
|
else:
|
|
relates_to_inbox_object = get_inbox_object_by_ap_id(
|
|
db,
|
|
ra.activity_object_ap_id,
|
|
)
|
|
|
|
inbox_object = models.InboxObject(
|
|
server=urlparse(ra.ap_id).netloc,
|
|
actor_id=actor.id,
|
|
ap_actor_id=actor.ap_id,
|
|
ap_type=ra.ap_type,
|
|
ap_id=ra.ap_id,
|
|
ap_context=ra.context,
|
|
ap_published_at=ap_published_at,
|
|
ap_object=ra.ap_object,
|
|
visibility=ra.visibility,
|
|
relates_to_inbox_object_id=relates_to_inbox_object.id
|
|
if relates_to_inbox_object
|
|
else None,
|
|
relates_to_outbox_object_id=relates_to_outbox_object.id
|
|
if relates_to_outbox_object
|
|
else None,
|
|
activity_object_ap_id=ra.activity_object_ap_id,
|
|
# Hide replies from the stream
|
|
is_hidden_from_stream=True if ra.in_reply_to else False,
|
|
)
|
|
|
|
db.add(inbox_object)
|
|
db.flush()
|
|
db.refresh(inbox_object)
|
|
|
|
if ra.ap_type == "Create":
|
|
_handle_create_activity(db, actor, inbox_object)
|
|
elif ra.ap_type == "Update":
|
|
pass
|
|
elif ra.ap_type == "Delete":
|
|
if relates_to_inbox_object:
|
|
_handle_delete_activity(db, actor, relates_to_inbox_object)
|
|
else:
|
|
# TODO(ts): handle delete actor
|
|
logger.info(
|
|
f"Received a Delete for an unknown object: {ra.activity_object_ap_id}"
|
|
)
|
|
elif ra.ap_type == "Follow":
|
|
_handle_follow_follow_activity(db, actor, inbox_object)
|
|
elif ra.ap_type == "Undo":
|
|
if relates_to_inbox_object:
|
|
_handle_undo_activity(db, actor, inbox_object, relates_to_inbox_object)
|
|
else:
|
|
logger.info("Received Undo for an unknown activity")
|
|
elif ra.ap_type in ["Accept", "Reject"]:
|
|
if not relates_to_outbox_object:
|
|
logger.info(
|
|
f"Received {raw_object['type']} for an unknown activity: "
|
|
f"{ra.activity_object_ap_id}"
|
|
)
|
|
else:
|
|
if relates_to_outbox_object.ap_type == "Follow":
|
|
following = models.Following(
|
|
actor_id=actor.id,
|
|
outbox_object_id=relates_to_outbox_object.id,
|
|
ap_actor_id=actor.ap_id,
|
|
)
|
|
db.add(following)
|
|
else:
|
|
logger.info(
|
|
"Received an Accept for an unsupported activity: "
|
|
f"{relates_to_outbox_object.ap_type}"
|
|
)
|
|
elif ra.ap_type == "Like":
|
|
if not relates_to_outbox_object:
|
|
logger.info(
|
|
f"Received a like for an unknown activity: {ra.activity_object_ap_id}"
|
|
)
|
|
else:
|
|
relates_to_outbox_object.likes_count = models.OutboxObject.likes_count + 1
|
|
|
|
notif = models.Notification(
|
|
notification_type=models.NotificationType.LIKE,
|
|
actor_id=actor.id,
|
|
outbox_object_id=relates_to_outbox_object.id,
|
|
inbox_object_id=inbox_object.id,
|
|
)
|
|
db.add(notif)
|
|
elif raw_object["type"] == "Announce":
|
|
if relates_to_outbox_object:
|
|
# This is an announce for a local object
|
|
relates_to_outbox_object.announces_count = (
|
|
models.OutboxObject.announces_count + 1
|
|
)
|
|
|
|
notif = models.Notification(
|
|
notification_type=models.NotificationType.ANNOUNCE,
|
|
actor_id=actor.id,
|
|
outbox_object_id=relates_to_outbox_object.id,
|
|
inbox_object_id=inbox_object.id,
|
|
)
|
|
db.add(notif)
|
|
else:
|
|
# This is announce for a maybe unknown object
|
|
if relates_to_inbox_object:
|
|
logger.info("Nothing to do, we already know about this object")
|
|
else:
|
|
# Save it as an inbox object
|
|
if not ra.activity_object_ap_id:
|
|
raise ValueError("Should never happen")
|
|
announced_raw_object = ap.fetch(ra.activity_object_ap_id)
|
|
announced_actor = fetch_actor(db, ap.get_actor_id(announced_raw_object))
|
|
announced_object = RemoteObject(announced_raw_object, announced_actor)
|
|
announced_inbox_object = models.InboxObject(
|
|
server=urlparse(announced_object.ap_id).netloc,
|
|
actor_id=announced_actor.id,
|
|
ap_actor_id=announced_actor.ap_id,
|
|
ap_type=announced_object.ap_type,
|
|
ap_id=announced_object.ap_id,
|
|
ap_context=announced_object.context,
|
|
ap_published_at=announced_object.ap_published_at,
|
|
ap_object=announced_object.ap_object,
|
|
visibility=announced_object.visibility,
|
|
is_hidden_from_stream=True,
|
|
)
|
|
db.add(announced_inbox_object)
|
|
db.flush()
|
|
inbox_object.relates_to_inbox_object_id = announced_inbox_object.id
|
|
elif ra.ap_type in ["Like", "Announce"]:
|
|
if not relates_to_outbox_object:
|
|
logger.info(
|
|
f"Received {ra.ap_type} for an unknown activity: "
|
|
f"{ra.activity_object_ap_id}"
|
|
)
|
|
else:
|
|
if ra.ap_type == "Like":
|
|
# TODO(ts): notification
|
|
relates_to_outbox_object.likes_count = (
|
|
models.OutboxObject.likes_count + 1
|
|
)
|
|
|
|
notif = models.Notification(
|
|
notification_type=models.NotificationType.LIKE,
|
|
actor_id=actor.id,
|
|
outbox_object_id=relates_to_outbox_object.id,
|
|
inbox_object_id=inbox_object.id,
|
|
)
|
|
db.add(notif)
|
|
elif raw_object["type"] == "Announce":
|
|
# TODO(ts): notification
|
|
relates_to_outbox_object.announces_count = (
|
|
models.OutboxObject.announces_count + 1
|
|
)
|
|
|
|
notif = models.Notification(
|
|
notification_type=models.NotificationType.ANNOUNCE,
|
|
actor_id=actor.id,
|
|
outbox_object_id=relates_to_outbox_object.id,
|
|
inbox_object_id=inbox_object.id,
|
|
)
|
|
db.add(notif)
|
|
else:
|
|
raise ValueError("Should never happpen")
|
|
|
|
else:
|
|
logger.warning(f"Received an unknown {inbox_object.ap_type} object")
|
|
|
|
db.commit()
|
|
|
|
|
|
def public_outbox_objects_count(db: Session) -> int:
|
|
return (
|
|
db.query(models.OutboxObject)
|
|
.filter(
|
|
models.OutboxObject.visibility == ap.VisibilityEnum.PUBLIC,
|
|
models.OutboxObject.is_deleted.is_(False),
|
|
)
|
|
.count()
|
|
)
|
|
|
|
|
|
def fetch_collection(db: Session, url: str) -> list[ap.RawObject]:
|
|
if url.startswith(config.BASE_URL):
|
|
if url == config.BASE_URL + "/followers":
|
|
q = db.query(models.Follower).options(joinedload(models.Follower.actor))
|
|
return [follower.actor.ap_actor for follower in q.all()]
|
|
else:
|
|
raise ValueError(f"internal collection for {url}) not supported")
|
|
|
|
return ap.parse_collection(url)
|