---
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