Ash McAllan: 1 quote RT and proper mf2 formatting for reposts and attachments. 7 files changed, 79 insertions(+), 11 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~tsileo/microblog.pub-devel/patches/37150/mbox | git am -3Learn more about email & git
--- app/admin.py | 2 ++ app/ap_object.py | 6 ++++++ app/boxes.py | 29 +++++++++++++++++++++++++++++ app/main.py | 14 ++++++++++++++ app/templates/admin_new.html | 1 + app/templates/index.html | 7 ++----- app/templates/utils.html | 31 +++++++++++++++++++++++++------ 7 files changed, 79 insertions(+), 11 deletions(-) diff --git a/app/admin.py b/app/admin.py index e837c46..484aadf 100644 --- a/app/admin.py +++ b/app/admin.py @@ -1105,6 +1105,7 @@ async def admin_actions_new( in_reply_to: str | None = Form(None), content_warning: str | None = Form(None), is_sensitive: bool = Form(False), + is_quote: bool = Form(False), visibility: str = Form(), poll_type: str | None = Form(None), name: str | None = Form(None), @@ -1163,6 +1164,7 @@ async def admin_actions_new( poll_answers=poll_answers, poll_duration_in_minutes=poll_duration_in_minutes, name=name, + is_quote=is_quote ) return RedirectResponse( request.url_for("outbox_by_public_id", public_id=public_id), diff --git a/app/ap_object.py b/app/ap_object.py index 52061b8..cbd878e 100644 --- a/app/ap_object.py +++ b/app/ap_object.py @@ -212,6 +212,12 @@ class Object: def in_reply_to(self) -> str | None: return self.ap_object.get("inReplyTo") + @property + def quote_url(self) -> str | None: + for tag in ap.as_list(self.ap_object.get("tag", [])): + if tag["type"] == "Link" and tag["name"][:3] == "RE:" and "href" in tag: + return tag["href"] + @property def is_in_reply_to_from_inbox(self) -> bool | None: if not self.in_reply_to: diff --git a/app/boxes.py b/app/boxes.py index 19a92f8..d675098 100644 --- a/app/boxes.py +++ b/app/boxes.py @@ -561,6 +561,14 @@ async def send_self_destruct(db_session: AsyncSession) -> None: await db_session.commit() +def quote_tag(url): + return { + "type": "Link", + "mediaType": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", + "name": "RE: "+url, + "href": url, + } + async def send_create( db_session: AsyncSession, @@ -575,6 +583,7 @@ async def send_create( poll_answers: list[str] | None = None, poll_duration_in_minutes: int | None = None, name: str | None = None, + is_quote: bool = False, ) -> str: note_id = allocate_outbox_id() published = now().replace(microsecond=0).isoformat().replace("+00:00", "Z") @@ -601,6 +610,10 @@ async def send_create( context = in_reply_to_object.ap_context conversation = in_reply_to_object.ap_context + if is_quote: + tags.append(quote_tag(in_reply_to)) + in_reply_to = None + for (upload, filename, alt_text) in uploads: attachments.append(upload_to_attachment(upload, filename, alt_text)) @@ -2714,3 +2727,19 @@ async def get_replies_tree( ) root_node.children = _get_reply_node_children(root_node, nodes_by_in_reply_to) return root_node + +async def get_quote(db_session,anybox_object): + logger.info("calling get_quote for "+anybox_object.ap_id) + if "tag" not in anybox_object.ap_object: + logger.info("no tags") + return + for tag in anybox_object.ap_object["tag"]: + if tag["type"] == "Link" and tag["name"][:3] == "RE:": + logger.info("looking for "+tag["href"]) + try_quote = await get_anybox_object_by_ap_id(db_session,tag["href"]) + if try_quote: + tag["content"] = try_quote + await get_quote(db_session, tag["content"]) + else: + logger.info("Couldnt find object "+tag["href"]) + diff --git a/app/main.py b/app/main.py index d0063dc..0f1c9c2 100644 --- a/app/main.py +++ b/app/main.py @@ -327,6 +327,9 @@ async def index( ) outbox_objects = outbox_objects_result.unique().all() + for outbox_object in outbox_objects: + await boxes.get_quote(db_session, outbox_object) + return await templates.render_template( db_session, request, @@ -770,6 +773,10 @@ async def outbox_by_public_id( f"{BASE_URL}/articles/{public_id[:7]}/{maybe_object.slug}", status_code=301, ) + await boxes.get_quote(db_session, maybe_object) + + if "quote_url" not in maybe_object.ap_object: + maybe_object.conversation = None replies_tree = await boxes.get_replies_tree( db_session, @@ -929,6 +936,13 @@ async def outbox_activity_by_public_id( if not maybe_object: raise HTTPException(status_code=404) + quoteUrl = None + for tag in maybe_object.tags: + if tag["type"] == "Link" and tag["name"][:3] == "RE:": + quoteUrl = tag["url"] + if quoteUrl != None: + maybe_object.ap_object["quoteUrl"] = quoteUrl + await _check_outbox_object_acl(request, db_session, maybe_object, httpsig_info) return ActivityPubResponse(ap.wrap_object(maybe_object.ap_object)) diff --git a/app/templates/admin_new.html b/app/templates/admin_new.html index ac3b3db..f2c7f60 100644 --- a/app/templates/admin_new.html +++ b/app/templates/admin_new.html @@ -79,6 +79,7 @@ </p> <p> <input type="checkbox" name="is_sensitive" id="is_sensitive"> <label for="is_sensitive">Mark attachment(s) as sensitive</label> + <input type="checkbox" name="is_quote" id="is_quote"> <label for="is_quote">Quote original post in reply</label> </p> <input type="hidden" name="in_reply_to" value="{{ request.query_params.in_reply_to }}"> <p> diff --git a/app/templates/index.html b/app/templates/index.html index a6915a8..f7d0e0c 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -26,12 +26,9 @@ <div class="h-feed"> <data class="p-name" value="{{ local_actor.display_name}}'s notes"></data> {% for outbox_object in objects %} - {% if outbox_object.ap_type in ["Note", "Article", "Video", "Question"] %} + {% if outbox_object.ap_type in ["Note", "Article", "Video", "Question","Announce"] %} {{ utils.display_object(outbox_object) }} - {% elif outbox_object.ap_type == "Announce" %} - <div class="shared-header"><strong>{{ utils.display_tiny_actor_icon(local_actor) }} {{ local_actor.display_name | clean_html(local_actor) | safe }}</strong> shared <span title="{{ outbox_object.ap_published_at.isoformat() }}">{{ outbox_object.ap_published_at | timeago }}</span></div> - {{ utils.display_object(outbox_object.relates_to_anybox_object) }} - {% endif %} + {% endif %} {% endfor %} </div> diff --git a/app/templates/utils.html b/app/templates/utils.html index 2cf460a..9945d4c 100644 --- a/app/templates/utils.html +++ b/app/templates/utils.html @@ -247,7 +247,7 @@ {% macro display_tiny_actor_icon(actor) %} {% block display_tiny_actor_icon scoped %} - <img class="tiny-actor-icon" src="{{ actor.resized_icon_url }}" alt="{{ actor.display_name }}'s avatar"> + <img class="tiny-actor-icon" src="{{ actor.resized_icon_url }}"> {% endblock %} {% endmacro %} @@ -425,13 +425,13 @@ {% if attachment.type == "Image" or (attachment | has_media_type("image")) %} {% if attachment.url not in object.inlined_images %} <a class="media-link" href="{{ attachment.proxied_url }}" target="_blank"> - <img src="{{ attachment.resized_url or attachment.proxied_url }}"{% if attachment.name %} title="{{ attachment.name }}" alt="{{ attachment.name }}"{% endif %} class="attachment"> + <img src="{{ attachment.resized_url or attachment.proxied_url }}"{% if attachment.name %} title="{{ attachment.name }}" alt="{{ attachment.name }}"{% endif %} class="attachment u-photo"> </a> {% endif %} {% elif attachment.type == "Video" or (attachment | has_media_type("video")) %} - <video controls preload="metadata" src="{{ attachment.url | media_proxy_url }}"{% if attachment.name %} title="{{ attachment.name }}"{% endif %}></video> + <video controls preload="metadata" src="{{ attachment.url | media_proxy_url }}"{% if attachment.name %} title="{{ attachment.name }}"{% endif %} class="attachment u-video"></video> {% elif attachment.type == "Audio" or (attachment | has_media_type("audio")) %} - <audio controls preload="metadata" src="{{ attachment.url | media_proxy_url }}"{% if attachment.name%} title="{{ attachment.name }}"{% endif %} class="attachment"></audio> + <audio controls preload="metadata" src="{{ attachment.url | media_proxy_url }}"{% if attachment.name%} title="{{ attachment.name }}"{% endif %} class="attachment u-audio"></audio> {% elif attachment.type == "Link" %} <a href="{{ attachment.url }}" class="attachment">{{ attachment.url | truncate(64, True) }}</a> ({{ attachment.mimetype}}) {% else %} @@ -499,9 +499,28 @@ {% macro display_object(object, likes=[], shares=[], webmentions=[], expanded=False, actors_metadata={}, is_object_page=False, is_h_entry=True) %} {% block display_object scoped %} {% set is_article_mode = object.is_from_outbox and object.ap_type == "Article" and is_object_page %} -{% if object.ap_type in ["Note", "Article", "Video", "Page", "Question", "Event"] %} + +{% if object.ap_type == "Announce" %} + <div class="{% if is_h_entry %}h-entry{% endif %}" id="{{ object.permalink_id }}"> + <div class="shared-header"><strong><a class="p-author h-card" h-ref="{{ local_actor.url }}">{{ display_tiny_actor_icon(local_actor) }} {{ local_actor.display_name | clean_html(local_actor) | safe }}</a></strong> shared <span title="{{ object.ap_published_at.isoformat() }}">{{ object.ap_published_at | timeago }}</span></div> + <div class="h-cite u-repost-of"> + {{ display_object(object.relates_to_anybox_object,is_h_entry=False) }} + </div> + </div> +{% elif object.ap_type in ["Note", "Article", "Video", "Page", "Question", "Event"] %} <div class="ap-object {% if expanded %}ap-object-expanded {% endif %}{% if is_h_entry %}h-entry{% endif %}" id="{{ object.permalink_id }}"> + +{% if object.quote_url%} +<div class="h-cite u-repost-of qrt"> + {% for tag in object.tags %} + {% if tag.type == "Link" and tag.content%} + {{ display_object(tag.content) }} + {% endif %} + {% endfor %} +</div> +{% endif %} + {% if is_article_mode %} <data class="h-card"> <data class="u-photo" value="{{ local_actor.icon_url }}"></data> @@ -814,8 +833,8 @@ </div> {% endif %} - </div> {% endif %} + {% endblock %} {% endmacro %} -- 2.25.1
Hey! Thank you for the patch. About quoting support, I think I mentioned once I have a branch in progress to add support for it. It will be a bit different from your approach, as I am planning to add a new foreign key in DB to reference the quoted object (it will prevent making HTTP requests each time we need to display it). But I like the mf2 improvements, here is what I pushed based on yours: https://git.sr.ht/~tsileo/microblog.pub/commit/578581b4dcf1847cbfa74f4b14522e93337a025a Let me know if I missed something, but I tried to run the result through a mf2 parser, and I think it looks correct. Thank you!