Improve actor icons handling and admin
parent
951c74c40a
commit
f66e3f3995
15
app/actor.py
15
app/actor.py
|
@ -7,6 +7,7 @@ from sqlalchemy.orm import Session
|
|||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from app import activitypub as ap
|
||||
from app import media
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from app.models import Actor as ActorModel
|
||||
|
@ -78,6 +79,20 @@ class Actor:
|
|||
def public_key_id(self) -> str:
|
||||
return self.ap_actor["publicKey"]["id"]
|
||||
|
||||
@property
|
||||
def proxied_icon_url(self) -> str:
|
||||
if self.icon_url:
|
||||
return media.proxied_media_url(self.icon_url)
|
||||
else:
|
||||
return "/static/nopic.png"
|
||||
|
||||
@property
|
||||
def resized_icon_url(self) -> str:
|
||||
if self.icon_url:
|
||||
return media.resized_media_url(self.icon_url, 50)
|
||||
else:
|
||||
return "/static/nopic.png"
|
||||
|
||||
|
||||
class RemoteActor(Actor):
|
||||
def __init__(self, ap_actor: ap.RawObject) -> None:
|
||||
|
|
50
app/admin.py
50
app/admin.py
|
@ -140,6 +140,56 @@ def stream(
|
|||
)
|
||||
|
||||
|
||||
@router.get("/inbox")
|
||||
def admin_inbox(
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
) -> templates.TemplateResponse:
|
||||
inbox = (
|
||||
db.query(models.InboxObject)
|
||||
.options(
|
||||
joinedload(models.InboxObject.relates_to_inbox_object),
|
||||
joinedload(models.InboxObject.relates_to_outbox_object),
|
||||
)
|
||||
.order_by(models.InboxObject.ap_published_at.desc())
|
||||
.limit(20)
|
||||
.all()
|
||||
)
|
||||
return templates.render_template(
|
||||
db,
|
||||
request,
|
||||
"admin_inbox.html",
|
||||
{
|
||||
"inbox": inbox,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/outbox")
|
||||
def admin_outbox(
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
) -> templates.TemplateResponse:
|
||||
outbox = (
|
||||
db.query(models.OutboxObject)
|
||||
.options(
|
||||
joinedload(models.OutboxObject.relates_to_inbox_object),
|
||||
joinedload(models.OutboxObject.relates_to_outbox_object),
|
||||
)
|
||||
.order_by(models.OutboxObject.ap_published_at.desc())
|
||||
.limit(20)
|
||||
.all()
|
||||
)
|
||||
return templates.render_template(
|
||||
db,
|
||||
request,
|
||||
"admin_outbox.html",
|
||||
{
|
||||
"outbox": outbox,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/notifications")
|
||||
def get_notifications(
|
||||
request: Request, db: Session = Depends(get_db)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import base64
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
@ -11,6 +10,7 @@ from app import activitypub as ap
|
|||
from app.actor import LOCAL_ACTOR
|
||||
from app.actor import Actor
|
||||
from app.actor import RemoteActor
|
||||
from app.media import proxied_media_url
|
||||
from app.utils import opengraph
|
||||
|
||||
|
||||
|
@ -64,7 +64,7 @@ class Object:
|
|||
def attachments(self) -> list["Attachment"]:
|
||||
attachments = []
|
||||
for obj in self.ap_object.get("attachment", []):
|
||||
proxied_url = _proxied_url(obj["url"])
|
||||
proxied_url = proxied_media_url(obj["url"])
|
||||
attachments.append(
|
||||
Attachment.parse_obj(
|
||||
{
|
||||
|
@ -82,7 +82,7 @@ class Object:
|
|||
for link in ap.as_list(self.ap_object.get("url", [])):
|
||||
if (isinstance(link, dict)) and link.get("type") == "Link":
|
||||
if link.get("mediaType", "").startswith("video"):
|
||||
proxied_url = _proxied_url(link["href"])
|
||||
proxied_url = proxied_media_url(link["href"])
|
||||
attachments.append(
|
||||
Attachment(
|
||||
type="Video",
|
||||
|
@ -151,10 +151,6 @@ class BaseModel(pydantic.BaseModel):
|
|||
alias_generator = _to_camel
|
||||
|
||||
|
||||
def _proxied_url(url: str) -> str:
|
||||
return "/proxy/media/" + base64.urlsafe_b64encode(url.encode()).decode()
|
||||
|
||||
|
||||
class Attachment(BaseModel):
|
||||
type: str
|
||||
media_type: str
|
||||
|
|
|
@ -52,6 +52,11 @@ from app.uploads import UPLOAD_DIR
|
|||
# TODO(ts):
|
||||
#
|
||||
# Next:
|
||||
# - inbox/outbox admin
|
||||
# - no counters anymore?
|
||||
# - allow to show tags in the menu
|
||||
# - support update post with history
|
||||
# - inbox/outbox in the admin (as in show every objects)
|
||||
# - show likes/announces counter for outbox activities
|
||||
# - update actor support
|
||||
# - replies support
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import base64
|
||||
|
||||
from app.config import BASE_URL
|
||||
|
||||
SUPPORTED_RESIZE = [50, 740]
|
||||
|
||||
|
||||
def proxied_media_url(url: str) -> str:
|
||||
if url.startswith(BASE_URL):
|
||||
return url
|
||||
|
||||
return "/proxy/media/" + base64.urlsafe_b64encode(url.encode()).decode()
|
||||
|
||||
|
||||
def resized_media_url(url: str, size: int) -> str:
|
||||
if size not in SUPPORTED_RESIZE:
|
||||
raise ValueError(f"Unsupported resize {size}")
|
||||
if url.startswith(BASE_URL):
|
||||
return url
|
||||
return proxied_media_url(url) + f"/{size}"
|
|
@ -1,6 +1,7 @@
|
|||
import enum
|
||||
from typing import Any
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import JSON
|
||||
from sqlalchemy import Boolean
|
||||
|
@ -104,6 +105,15 @@ class InboxObject(Base, BaseObject):
|
|||
|
||||
og_meta: Mapped[list[dict[str, Any]] | None] = Column(JSON, nullable=True)
|
||||
|
||||
@property
|
||||
def relates_to_anybox_object(self) -> Union["InboxObject", "OutboxObject"] | None:
|
||||
if self.relates_to_inbox_object_id:
|
||||
return self.relates_to_inbox_object
|
||||
elif self.relates_to_outbox_object_id:
|
||||
return self.relates_to_outbox_object
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class OutboxObject(Base, BaseObject):
|
||||
__tablename__ = "outbox"
|
||||
|
@ -202,6 +212,15 @@ class OutboxObject(Base, BaseObject):
|
|||
)
|
||||
return out
|
||||
|
||||
@property
|
||||
def relates_to_anybox_object(self) -> Union["InboxObject", "OutboxObject"] | None:
|
||||
if self.relates_to_inbox_object_id:
|
||||
return self.relates_to_inbox_object
|
||||
elif self.relates_to_outbox_object_id:
|
||||
return self.relates_to_outbox_object
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class Follower(Base):
|
||||
__tablename__ = "follower"
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
{%- import "utils.html" as utils with context -%}
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
|
||||
{% for inbox_object in inbox %}
|
||||
{% if inbox_object.ap_type == "Announce" %}
|
||||
{{ utils.display_object(inbox_object.relates_to_anybox_object) }}
|
||||
{% elif inbox_object.ap_type in ["Article", "Note", "Video"] %}
|
||||
{{ utils.display_object(inbox_object) }}
|
||||
{% if inbox_object.liked_via_outbox_object_ap_id %}
|
||||
{{ utils.admin_undo_button(inbox_object.liked_via_outbox_object_ap_id, "Unlike") }}
|
||||
{% else %}
|
||||
{{ utils.admin_like_button(inbox_object.ap_id) }}
|
||||
{% endif %}
|
||||
{{ utils.admin_announce_button(inbox_object.ap_id) }}
|
||||
{{ utils.admin_reply_button(inbox_object.ap_id) }}
|
||||
{% else %}
|
||||
Implement {{ inbox_object.ap_type }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,24 @@
|
|||
{%- import "utils.html" as utils with context -%}
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
|
||||
{% for outbox_object in outbox %}
|
||||
|
||||
{% if outbox_object.ap_type == "Announce" %}
|
||||
{{ utils.display_object(outbox_object.relates_to_anybox_object) }}
|
||||
{% elif outbox_object.ap_type in ["Article", "Note", "Video"] %}
|
||||
{{ utils.display_object(outbox_object) }}
|
||||
{% if outbox_object.liked_via_outbox_object_ap_id %}
|
||||
{{ utils.admin_undo_button(outbox_object.liked_via_outbox_object_ap_id, "Unlike") }}
|
||||
{% else %}
|
||||
{{ utils.admin_like_button(outbox_object.ap_id) }}
|
||||
{% endif %}
|
||||
{{ utils.admin_announce_button(outbox_object.ap_id) }}
|
||||
{{ utils.admin_reply_button(outbox_object.ap_id) }}
|
||||
{% else %}
|
||||
Implement {{ outbox_object.ap_type }}
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
|
@ -4,23 +4,17 @@
|
|||
|
||||
{% for inbox_object in stream %}
|
||||
{% if inbox_object.ap_type == "Announce" %}
|
||||
{% if inbox_object.relates_to_inbox_object_id %}
|
||||
{{ utils.display_object(inbox_object.relates_to_inbox_object) }}
|
||||
{% else %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
{{ utils.display_object(inbox_object.relates_to_anybox_object) }}
|
||||
{% elif inbox_object.ap_type in ["Article", "Note", "Video"] %}
|
||||
{{ utils.display_object(inbox_object) }}
|
||||
{% if inbox_object.liked_via_outbox_object_ap_id %}
|
||||
{{ utils.admin_undo_button(inbox_object.liked_via_outbox_object_ap_id, "Unlike") }}
|
||||
{% else %}
|
||||
{{ utils.admin_like_button(inbox_object.ap_id) }}
|
||||
{% if inbox_object.liked_via_outbox_object_ap_id %}
|
||||
{{ utils.admin_undo_button(inbox_object.liked_via_outbox_object_ap_id, "Unlike") }}
|
||||
{% else %}
|
||||
{{ utils.admin_like_button(inbox_object.ap_id) }}
|
||||
{% endif %}
|
||||
{{ utils.admin_announce_button(inbox_object.ap_id) }}
|
||||
{{ utils.admin_reply_button(inbox_object.ap_id) }}
|
||||
{% endif %}
|
||||
|
||||
{{ utils.admin_announce_button(inbox_object.ap_id) }}
|
||||
{% endif %}
|
||||
{{ utils.admin_reply_button(inbox_object.ap_id) }}
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
<li>{{ admin_link("index", "Public") }}</li>
|
||||
<li>{{ admin_link("admin_new", "New") }}</li>
|
||||
<li>{{ admin_link("stream", "Stream") }}</li>
|
||||
<li>{{ admin_link("admin_inbox", "Inbox") }}</li>
|
||||
<li>{{ admin_link("admin_outbox", "Outbox") }}</li>
|
||||
<li>{{ admin_link("get_notifications", "Notifications") }} {% if notifications_count %}({{ notifications_count }}){% endif %}</li>
|
||||
<li>{{ admin_link("get_lookup", "Lookup") }}</li>
|
||||
<li><a href="">Bookmarks</a></li>
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
{% set metadata = actors_metadata.get(actor.ap_id) %}
|
||||
<div style="display: flex;column-gap: 20px;margin:20px 0 10px 0;" class="actor-box">
|
||||
<div style="flex: 0 0 48px;">
|
||||
<img src="{{ actor.icon_url | media_proxy_url }}/50" style="max-width:45px;">
|
||||
<img src="{{ actor.resized_icon_url }}" style="max-width:45px;">
|
||||
</div>
|
||||
<a href="{{ actor.url }}" style="">
|
||||
<div><strong>{{ actor.name or actor.preferred_username }}</strong></div>
|
||||
|
@ -97,12 +97,12 @@
|
|||
{% if object.ap_type in ["Note", "Article", "Video"] %}
|
||||
<div class="activity-wrap" id="{{ object.permalink_id }}">
|
||||
<div class="activity-content">
|
||||
<img src="{% if object.actor.icon_url %}{{ object.actor.icon_url | media_proxy_url }}/50{% else %}/static/nopic.png{% endif %}" alt="" class="actor-icon">
|
||||
<img src="{{ object.actor.resized_icon_url }}" alt="" class="actor-icon">
|
||||
<div class="activity-header">
|
||||
<strong>{{ object.actor.name or object.actor.preferred_username }}</strong>
|
||||
<span>{{ object.actor.handle }}</span>
|
||||
<span class="activity-date" title="{{ object.ap_published_at.isoformat() }}">
|
||||
{{ object.visibility }}
|
||||
{{ object.visibility.value }}
|
||||
<a href="{{ object.url }}">{{ object.ap_published_at | timeago }}</a>
|
||||
</span>
|
||||
<div class="activity-main">
|
||||
|
|
Loading…
Reference in New Issue