Improved replies support

main
Thomas Sileo 2022-06-24 11:33:05 +02:00
parent 7293160b6f
commit baceb6be6c
11 changed files with 67 additions and 17 deletions

View File

@ -93,13 +93,20 @@ def get_lookup(
def admin_new( def admin_new(
request: Request, request: Request,
query: str | None = None, query: str | None = None,
in_reply_to: str | None = None,
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> templates.TemplateResponse: ) -> templates.TemplateResponse:
in_reply_to_object = None
if in_reply_to:
in_reply_to_object = boxes.get_anybox_object_by_ap_id(db, in_reply_to)
if not in_reply_to_object:
raise ValueError(f"Unknown object {in_reply_to=}")
return templates.render_template( return templates.render_template(
db, db,
request, request,
"admin_new.html", "admin_new.html",
{}, {"in_reply_to_object": in_reply_to_object},
) )
@ -237,6 +244,7 @@ def admin_actions_new(
files: list[UploadFile], files: list[UploadFile],
content: str = Form(), content: str = Form(),
redirect_url: str = Form(), redirect_url: str = Form(),
in_reply_to: str | None = Form(),
csrf_check: None = Depends(verify_csrf_token), csrf_check: None = Depends(verify_csrf_token),
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> RedirectResponse: ) -> RedirectResponse:
@ -246,7 +254,12 @@ def admin_actions_new(
for f in files: for f in files:
upload = save_upload(db, f) upload = save_upload(db, f)
uploads.append((upload, f.filename)) uploads.append((upload, f.filename))
public_id = boxes.send_create(db, source=content, uploads=uploads) public_id = boxes.send_create(
db,
source=content,
uploads=uploads,
in_reply_to=in_reply_to or None,
)
return RedirectResponse( return RedirectResponse(
request.url_for("outbox_by_public_id", public_id=public_id), request.url_for("outbox_by_public_id", public_id=public_id),
status_code=302, status_code=302,

View File

@ -54,15 +54,14 @@ class Object:
@property @property
def context(self) -> str | None: def context(self) -> str | None:
return self.ap_object.get("context") return self.ap_object.get("context") or self.ap_object.get("conversation")
@property @property
def sensitive(self) -> bool: def sensitive(self) -> bool:
return self.ap_object.get("sensitive", False) return self.ap_object.get("sensitive", False)
@property @property
def attachments_old(self) -> list["Attachment"]: def attachments(self) -> list["Attachment"]:
# TODO: set img_src with the proxy URL (proxy_url?)
attachments = [] attachments = []
for obj in self.ap_object.get("attachment", []): for obj in self.ap_object.get("attachment", []):
proxied_url = _proxied_url(obj["url"]) proxied_url = _proxied_url(obj["url"])

View File

@ -20,10 +20,12 @@ from app.ap_object import RemoteObject
from app.config import BASE_URL from app.config import BASE_URL
from app.config import ID from app.config import ID
from app.database import now from app.database import now
from app.process_outgoing_activities import new_outgoing_activity from app.outgoing_activities import new_outgoing_activity
from app.source import markdownify from app.source import markdownify
from app.uploads import upload_to_attachment from app.uploads import upload_to_attachment
AnyboxObject = models.InboxObject | models.OutboxObject
def allocate_outbox_id() -> str: def allocate_outbox_id() -> str:
return uuid.uuid4().hex return uuid.uuid4().hex
@ -219,6 +221,7 @@ def send_create(
db: Session, db: Session,
source: str, source: str,
uploads: list[tuple[models.Upload, str]], uploads: list[tuple[models.Upload, str]],
in_reply_to: str | None,
) -> str: ) -> str:
note_id = allocate_outbox_id() note_id = allocate_outbox_id()
published = now().replace(microsecond=0).isoformat().replace("+00:00", "Z") published = now().replace(microsecond=0).isoformat().replace("+00:00", "Z")
@ -226,6 +229,14 @@ def send_create(
content, tags = markdownify(db, source) content, tags = markdownify(db, source)
attachments = [] attachments = []
if in_reply_to:
in_reply_to_object = get_anybox_object_by_ap_id(db, in_reply_to)
if not in_reply_to_object:
raise ValueError(f"Invalid in reply to {in_reply_to=}")
if not in_reply_to_object.context:
raise ValueError("Object has no context")
context = in_reply_to_object.context
for (upload, filename) in uploads: for (upload, filename) in uploads:
attachments.append(upload_to_attachment(upload, filename)) attachments.append(upload_to_attachment(upload, filename))
@ -243,7 +254,7 @@ def send_create(
"url": outbox_object_id(note_id), "url": outbox_object_id(note_id),
"tag": tags, "tag": tags,
"summary": None, "summary": None,
"inReplyTo": None, "inReplyTo": in_reply_to,
"sensitive": False, "sensitive": False,
"attachment": attachments, "attachment": attachments,
} }
@ -331,6 +342,13 @@ def get_outbox_object_by_ap_id(db: Session, ap_id: str) -> models.OutboxObject |
) )
def get_anybox_object_by_ap_id(db: Session, ap_id: str) -> AnyboxObject | None:
if ap_id.startswith(BASE_URL):
return get_outbox_object_by_ap_id(db, ap_id)
else:
return get_inbox_object_by_ap_id(db, ap_id)
def _handle_delete_activity( def _handle_delete_activity(
db: Session, db: Session,
from_actor: models.Actor, from_actor: models.Actor,
@ -538,14 +556,18 @@ def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
else None, else None,
activity_object_ap_id=ra.activity_object_ap_id, activity_object_ap_id=ra.activity_object_ap_id,
# Hide replies from the stream # Hide replies from the stream
is_hidden_from_stream=True if ra.in_reply_to else False, is_hidden_from_stream=(
True
if (ra.in_reply_to and not ra.in_reply_to.startswith(BASE_URL))
else False
), # TODO: handle mentions
) )
db.add(inbox_object) db.add(inbox_object)
db.flush() db.flush()
db.refresh(inbox_object) db.refresh(inbox_object)
if ra.ap_type == "Create": if ra.ap_type == "Note": # TODO: handle create better
_handle_create_activity(db, actor, inbox_object) _handle_create_activity(db, actor, inbox_object)
elif ra.ap_type == "Update": elif ra.ap_type == "Update":
pass pass

View File

@ -63,6 +63,7 @@ class InboxObject(Base, BaseObject):
ap_published_at = Column(DateTime(timezone=True), nullable=False) ap_published_at = Column(DateTime(timezone=True), nullable=False)
ap_object: Mapped[ap.RawObject] = Column(JSON, nullable=False) ap_object: Mapped[ap.RawObject] = Column(JSON, nullable=False)
# Only set for activities
activity_object_ap_id = Column(String, nullable=True) activity_object_ap_id = Column(String, nullable=True)
visibility = Column(Enum(ap.VisibilityEnum), nullable=False) visibility = Column(Enum(ap.VisibilityEnum), nullable=False)
@ -242,8 +243,6 @@ class NotificationType(str, enum.Enum):
UNDO_LIKE = "undo_like" UNDO_LIKE = "undo_like"
ANNOUNCE = "announce" ANNOUNCE = "announce"
UNDO_ANNOUNCE = "undo_announce" UNDO_ANNOUNCE = "undo_announce"
# TODO:
MENTION = "mention" MENTION = "mention"

View File

@ -2,10 +2,16 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
In reply to:
{% if in_reply_to_object %}
{{ utils.display_object(in_reply_to_object) }}
{% endif %}
<form action="{{ request.url_for("admin_actions_new") }}" enctype="multipart/form-data" method="POST"> <form action="{{ request.url_for("admin_actions_new") }}" enctype="multipart/form-data" method="POST">
{{ utils.embed_csrf_token() }} {{ utils.embed_csrf_token() }}
{{ utils.embed_redirect_url() }} {{ utils.embed_redirect_url() }}
<textarea name="content" rows="10" cols="50" autofocus="autofocus" designMode="on" style="font-size:1.2em;width:95%;"></textarea> <textarea name="content" rows="10" cols="50" autofocus="autofocus" designMode="on" style="font-size:1.2em;width:95%;"></textarea>
<input type="hidden" name="in_reply_to" value="{{ request.query_params.in_reply_to }}">
<input name="files" type="file" multiple> <input name="files" type="file" multiple>
<input type="submit" value="Publish"> <input type="submit" value="Publish">
</form> </form>

View File

@ -20,6 +20,7 @@
{{ utils.admin_announce_button(inbox_object.ap_id) }} {{ utils.admin_announce_button(inbox_object.ap_id) }}
{% endif %} {% endif %}
{{ utils.admin_reply_button(inbox_object.ap_id) }}
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}

View File

@ -15,8 +15,8 @@
{% if is_admin %} {% if is_admin %}
<div id="admin"> <div id="admin">
{% macro admin_link(url, text) %} {% macro admin_link(url, text) %}
{% set url_for = request.url_for(url) %} {% set url_for = request.app.router.url_path_for(url) %}
<a href="{{ url_for }}" {% if request.url == url_for %}class="active"{% endif %}>{{ text }}</a> <a href="{{ url_for }}" {% if request.url.path == url_for %}class="active"{% endif %}>{{ text }}</a>
{% endmacro %} {% endmacro %}
<div style="margin-bottom:30px;"> <div style="margin-bottom:30px;">
<nav class="flexbox"> <nav class="flexbox">

View File

@ -52,6 +52,13 @@
</form> </form>
{% endmacro %} {% endmacro %}
{% macro admin_reply_button(ap_object_id) %}
<form action="/admin/new" method="GET">
<input type="hidden" name="in_reply_to" value="{{ ap_object_id }}">
<button type="submit">Reply</button>
</form>
{% endmacro %}
{% macro display_actor(actor, actors_metadata) %} {% macro display_actor(actor, actors_metadata) %}
{{ actors_metadata }} {{ actors_metadata }}
{% set metadata = actors_metadata.get(actor.ap_id) %} {% set metadata = actors_metadata.get(actor.ap_id) %}
@ -95,6 +102,7 @@
<strong>{{ object.actor.name or object.actor.preferred_username }}</strong> <strong>{{ object.actor.name or object.actor.preferred_username }}</strong>
<span>{{ object.actor.handle }}</span> <span>{{ object.actor.handle }}</span>
<span class="activity-date" title="{{ object.ap_published_at.isoformat() }}"> <span class="activity-date" title="{{ object.ap_published_at.isoformat() }}">
{{ object.visibility }}
<a href="{{ object.url }}">{{ object.ap_published_at | timeago }}</a> <a href="{{ object.url }}">{{ object.ap_published_at | timeago }}</a>
</span> </span>
<div class="activity-main"> <div class="activity-main">

View File

@ -51,7 +51,9 @@ def uvicorn(ctx):
@task @task
def process_outgoing_activities(ctx): def process_outgoing_activities(ctx):
# type: (Context) -> None # type: (Context) -> None
run("poetry run python app/process_outgoing_activities.py", pty=True, echo=True) from app.outgoing_activities import loop
loop()
@task @task

View File

@ -8,9 +8,9 @@ from app import models
from app.actor import LOCAL_ACTOR from app.actor import LOCAL_ACTOR
from app.ap_object import RemoteObject from app.ap_object import RemoteObject
from app.database import Session from app.database import Session
from app.process_outgoing_activities import _MAX_RETRIES from app.outgoing_activities import _MAX_RETRIES
from app.process_outgoing_activities import new_outgoing_activity from app.outgoing_activities import new_outgoing_activity
from app.process_outgoing_activities import process_next_outgoing_activity from app.outgoing_activities import process_next_outgoing_activity
from tests import factories from tests import factories