Basic OpenGraph support (#267)
Creates an OpenGraph template include in base.html including the basic tags expected on all pages. Then allows any page to add additional expected tags via `context`. Currently, profiles and posts are enriched to show complete opengraph metadata, and render correctly in Discord. Note: This does not show posts in Slack like Twitter/Mastodon do. I believe this is due to Slack preferring oembed when present, which is a mastodon API endpoint we may need to create at some point.
This commit is contained in:
parent
dab8dd59a7
commit
b53504fe64
|
@ -838,6 +838,22 @@ class Post(StatorModel):
|
|||
raise ValueError("Actor on delete does not match object")
|
||||
post.delete()
|
||||
|
||||
### OpenGraph API ###
|
||||
|
||||
def to_opengraph_dict(self) -> dict:
|
||||
return {
|
||||
"og:title": f"{self.author.name} (@{self.author.handle})",
|
||||
"og:type": "article",
|
||||
"og:published_time": (self.published or self.created).isoformat(),
|
||||
"og:modified_time": (
|
||||
self.edited or self.published or self.created
|
||||
).isoformat(),
|
||||
"og:description": (self.summary or self.safe_content_local()),
|
||||
"og:image:url": self.author.local_icon_url().absolute,
|
||||
"og:image:height": 85,
|
||||
"og:image:width": 85,
|
||||
}
|
||||
|
||||
### Mastodon API ###
|
||||
|
||||
def to_mastodon_json(self, interactions=None):
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def dict_merge(base: dict, defaults: dict):
|
||||
"""
|
||||
Merges two input dictionaries, returning the merged result.
|
||||
|
||||
`input|dict_merge:defaults`
|
||||
|
||||
The defaults are overridden by any key present in the `input` dict.
|
||||
"""
|
||||
if not (isinstance(base, dict) or isinstance(defaults, dict)):
|
||||
raise ValueError("Filter inputs must be dictionaries")
|
||||
|
||||
result = {}
|
||||
|
||||
result.update(defaults)
|
||||
result.update(base)
|
||||
|
||||
return result
|
|
@ -39,11 +39,15 @@ class Individual(TemplateView):
|
|||
# Show normal page
|
||||
return super().get(request)
|
||||
|
||||
def get_context_data(self):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
ancestors, descendants = PostService(self.post_obj).context(
|
||||
self.request.identity
|
||||
)
|
||||
return {
|
||||
|
||||
context.update(
|
||||
{
|
||||
"identity": self.identity,
|
||||
"post": self.post_obj,
|
||||
"interactions": PostInteraction.get_post_interactions(
|
||||
|
@ -54,6 +58,9 @@ class Individual(TemplateView):
|
|||
"ancestors": ancestors,
|
||||
"descendants": descendants,
|
||||
}
|
||||
)
|
||||
|
||||
return context
|
||||
|
||||
def serve_object(self):
|
||||
# If this not a local post, redirect to its canonical URI
|
||||
|
|
|
@ -8,4 +8,10 @@ def config_context(request):
|
|||
request.identity.config_identity if request.identity else None
|
||||
),
|
||||
"top_section": request.path.strip("/").split("/")[0],
|
||||
"opengraph_defaults": {
|
||||
"og:site_name": Config.system.site_name,
|
||||
"og:type": "website",
|
||||
"og:title": Config.system.site_name,
|
||||
"og:url": request.build_absolute_uri(),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
{% load opengraph %}
|
||||
{% with opengraph_merged=opengraph_local|dict_merge:opengraph_defaults %}
|
||||
<!-- Begin OpenGraph tagging -->
|
||||
{% for key, value in opengraph_merged.items %}
|
||||
<meta content="{{ value|striptags }}" property="{{ key }}"/>
|
||||
{% if key == "og:description" %}
|
||||
{# Mastodon duplicates this one tag without the og: prefix. Not sure why #}
|
||||
<meta content="{{ value|striptags }}" property="description"/>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% block opengraph_extra %}
|
||||
{% endblock %}
|
||||
<!-- End OpenGraph tagging -->
|
||||
{% endwith %}
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
{% block title %}Post by {{ post.author.html_name_or_handle }}{% endblock %}
|
||||
|
||||
{% block opengraph %}
|
||||
{% include "_opengraph.html" with opengraph_local=post.to_opengraph_dict %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% for ancestor in ancestors reversed %}
|
||||
{% include "activities/_post.html" with post=ancestor reply=True link_original=False %}
|
||||
|
|
|
@ -26,6 +26,9 @@
|
|||
{% if config_identity.custom_css %}
|
||||
<style>{{ config_identity.custom_css }}</style>
|
||||
{% endif %}
|
||||
{% block opengraph %}
|
||||
{% include "_opengraph.html" with opengraph_local=opengraph_defaults %}
|
||||
{% endblock %}
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
<body class="{% block body_class %}{% endblock %}" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
{% block title %}{{ identity }}{% endblock %}
|
||||
|
||||
{% block opengraph %}
|
||||
{% include "_opengraph.html" with opengraph_local=identity.to_opengraph_dict %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
{% if identity.local %}
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS feed for {{ identity.name }}" href="rss/" />
|
||||
|
|
|
@ -735,6 +735,19 @@ class Identity(StatorModel):
|
|||
await sync_to_async(self.save)()
|
||||
return True
|
||||
|
||||
### OpenGraph API ###
|
||||
|
||||
def to_opengraph_dict(self) -> dict:
|
||||
return {
|
||||
"og:title": f"{self.name} (@{self.handle})",
|
||||
"og:type": "profile",
|
||||
"og:description": self.summary,
|
||||
"og:profile:username": self.handle,
|
||||
"og:image:url": self.local_icon_url().absolute,
|
||||
"og:image:height": 85,
|
||||
"og:image:width": 85,
|
||||
}
|
||||
|
||||
### Mastodon Client API ###
|
||||
|
||||
def to_mastodon_json(self):
|
||||
|
|
Loading…
Reference in New Issue