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.
pull/276/head^2
Corry Haines 2022-12-26 09:39:33 -08:00 zatwierdzone przez GitHub
rodzic dab8dd59a7
commit b53504fe64
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
9 zmienionych plików z 102 dodań i 12 usunięć

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -39,21 +39,28 @@ 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 {
"identity": self.identity,
"post": self.post_obj,
"interactions": PostInteraction.get_post_interactions(
[self.post_obj] + ancestors + descendants,
self.request.identity,
),
"link_original": True,
"ancestors": ancestors,
"descendants": descendants,
}
context.update(
{
"identity": self.identity,
"post": self.post_obj,
"interactions": PostInteraction.get_post_interactions(
[self.post_obj] + ancestors + descendants,
self.request.identity,
),
"link_original": True,
"ancestors": ancestors,
"descendants": descendants,
}
)
return context
def serve_object(self):
# If this not a local post, redirect to its canonical URI

Wyświetl plik

@ -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(),
},
}

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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