Improve actor icons handling and admin

main
Thomas Sileo 2022-06-25 08:23:28 +02:00
parent 951c74c40a
commit f66e3f3995
11 changed files with 172 additions and 25 deletions

View File

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

View File

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

View File

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

View File

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

20
app/media.py 100644
View File

@ -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}"

View File

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

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

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

View File

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