Add meta tags for better previews when sharing links

This commit is contained in:
Joakim Hellsén 2026-04-27 19:22:19 +02:00
commit 73c8e9da39
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
14 changed files with 177 additions and 22 deletions

View file

@ -1,7 +1,10 @@
{% extends "base.html" %}
{% block title %}
| Add new feed
Add Feed | discord-rss-bot
{% endblock title %}
{% block description %}
Add a new RSS or Atom feed and attach it to a Discord webhook for delivery.
{% endblock description %}
{% block content %}
<div class="p-2 border border-dark">
<form action="/add" method="post">

View file

@ -1,7 +1,6 @@
{% extends "base.html" %}
{% block title %}
| Add new webhook
{% endblock title %}
{% block title %}Add Webhook | discord-rss-bot{% endblock title %}
{% block description %}Register a Discord webhook so feeds can post updates into your server or thread.{% endblock description %}
{% block content %}
<div class="p-2 border border-dark">
<form action="/add_webhook" method="post">

View file

@ -3,17 +3,38 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>
{% block title %}
discord-rss-bot
{% endblock title %}
</title>
<meta name="description"
content="Stay updated with the latest news and events with our easy-to-use RSS bot. Never miss a message or announcement again with real-time notifications directly to your Discord server." />
content="{% block description %}Manage RSS feeds, webhooks, filters, and message delivery settings for Discord updates from a single dashboard.{% endblock description %}" />
{% set page_title = self.title() | striptags | trim %}
{% set page_description = self.description() | striptags | trim %}
{% set canonical_url = request.url %}
{% set social_image = request.base_url ~ "static/favicon.ico" %}
<meta name="keywords"
content="discord, rss, bot, notifications, announcements, updates, real-time, server, messages, news, events, feed." />
<meta name="theme-color" content="#212529" />
<meta name="color-scheme" content="dark" />
<meta name="robots" content="noindex, nofollow">
<link rel="canonical" href="{{ canonical_url }}" />
<meta property="og:site_name" content="discord-rss-bot" />
<meta property="og:type" content="website" />
<meta property="og:title" content="{{ page_title }}" />
<meta property="og:description" content="{{ page_description }}" />
<meta property="og:url" content="{{ canonical_url }}" />
<meta property="og:image" content="{{ social_image }}" />
<meta property="og:image:type" content="image/x-icon" />
<meta property="og:image:alt" content="discord-rss-bot icon" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="{{ page_title }}" />
<meta name="twitter:description" content="{{ page_description }}" />
<meta name="twitter:image" content="{{ social_image }}" />
<link href="/static/bootstrap.min.css" rel="stylesheet" />
<link href="/static/styles.css" rel="stylesheet" />
<link rel="icon" href="/static/favicon.ico" type="image/x-icon" />
<title>discord-rss-bot
{% block title %}
{% endblock title %}
</title>
{% block head %}
{% endblock head %}
</head>

View file

@ -1,7 +1,10 @@
{% extends "base.html" %}
{% block title %}
| Blacklist
Blacklist: {{ feed.title if feed.title else feed.url }} | discord-rss-bot
{% endblock title %}
{% block description %}
Block matching entries from {{ feed.title if feed.title else feed.url }} before they are delivered to Discord.
{% endblock description %}
{% block content %}
<div class="row g-3 filter-page">
<div class="col-lg-5">

View file

@ -1,7 +1,6 @@
{% extends "base.html" %}
{% block title %}
| Custom message
{% endblock title %}
{% block title %}Message Template: {{ feed.title if feed.title else feed.url }} | discord-rss-bot{% endblock title %}
{% block description %}Customize the plain text Discord message template for {{ feed.title if feed.title else feed.url }}.{% endblock description %}
{% block content %}
<div class="p-2 border border-dark">
<form action="/custom" method="post">

View file

@ -1,7 +1,6 @@
{% extends "base.html" %}
{% block title %}
| Embed
{% endblock title %}
{% block title %}Embed Template: {{ feed.title if feed.title else feed.url }} | discord-rss-bot{% endblock title %}
{% block description %}Customize the Discord embed layout, colors, and media for {{ feed.title if feed.title else feed.url }}.{% endblock description %}
{% block content %}
<div class="p-2 border border-dark">
<form action="/embed" method="post">

View file

@ -1,7 +1,10 @@
{% extends "base.html" %}
{% block title %}
| {{ feed.title }}
{{ feed.title if feed.title else feed.url }} | discord-rss-bot
{% endblock title %}
{% block description %}
Review feed health, delivery settings, filters, webhook attachment, and update intervals for {{ feed.title if feed.title else feed.url }}.
{% endblock description %}
{% block content %}
<div class="row g-3 feed-page">
<div class="col-12">

View file

@ -1,4 +1,10 @@
{% extends "base.html" %}
{% block title %}
Feeds Dashboard | discord-rss-bot
{% endblock title %}
{% block description %}
View configured feeds, broken sources, webhook groups, and delivery status across your Discord RSS bot dashboard.
{% endblock description %}
{% block content %}
<!-- List all feeds -->
<ul>

View file

@ -1,7 +1,12 @@
{% extends "base.html" %}
{% block title %}
| Search
Search
{% if query %}: {{ query }}{% endif %}
| discord-rss-bot
{% endblock title %}
{% block description %}
Browse search results for {{ query if query else "your query" }} across tracked feed entries and feeds.
{% endblock description %}
{% block content %}
<div class="p-2 border border-dark text-muted">
Your search for "{{ query }}" returned {{ search_amount.total }} results.

View file

@ -1,7 +1,10 @@
{% extends "base.html" %}
{% block title %}
| Settings
Settings | discord-rss-bot
{% endblock title %}
{% block description %}
Adjust default update intervals, delivery modes, and screenshot layout for feeds managed by your bot.
{% endblock description %}
{% block content %}
<section>
<div class="text-light">

View file

@ -1,7 +1,10 @@
{% extends "base.html" %}
{% block title %}
| {{ webhook_name }}
{{ webhook_name }} | discord-rss-bot
{% endblock title %}
{% block description %}
Review webhook settings, attached feeds, and latest entries delivered to {{ webhook_name }}.
{% endblock description %}
{% block content %}
{% if message %}<div class="alert alert-info" role="alert">{{ message }}</div>{% endif %}
<div class="card mb-3 border border-dark p-3 text-light">

View file

@ -1,7 +1,10 @@
{% extends "base.html" %}
{% block title %}
| Webhooks
Webhooks | discord-rss-bot
{% endblock title %}
{% block description %}
Manage stored Discord webhooks and inspect the delivery endpoints connected to your feeds.
{% endblock description %}
{% block content %}
<div class="container my-4 text-light">
{% for hook in hooks_with_data %}

View file

@ -1,7 +1,10 @@
{% extends "base.html" %}
{% block title %}
| Whitelist
Whitelist: {{ feed.title if feed.title else feed.url }} | discord-rss-bot
{% endblock title %}
{% block description %}
Allow only matching entries from {{ feed.title if feed.title else feed.url }} to reach Discord.
{% endblock description %}
{% block content %}
<div class="row g-3 filter-page">
<div class="col-lg-5">

View file

@ -46,6 +46,26 @@ def ensure_preview_feed_exists() -> Reader:
return reader
def assert_social_preview_metadata(
response: Response,
*,
title: str,
description: str,
) -> None:
assert response.status_code == 200, f"Expected page to render successfully: {response.text}"
assert f"<title>{title}</title>" in response.text
assert re.search(
rf'<meta name="description"\s+content="{re.escape(description)}"\s*/>',
response.text,
)
assert f'<meta property="og:title" content="{title}" />' in response.text
assert f'<meta property="og:description" content="{description}" />' in response.text
assert '<meta property="og:site_name" content="discord-rss-bot" />' in response.text
assert '<meta name="twitter:card" content="summary" />' in response.text
assert f'<meta name="twitter:title" content="{title}" />' in response.text
assert f'<meta name="twitter:description" content="{description}" />' in response.text
def test_search() -> None:
"""Test the /search page."""
# Remove the feed if it already exists before we run the test.
@ -230,6 +250,91 @@ def test_get() -> None:
assert response.status_code == 200, f"/whitelist failed: {response.text}"
def test_views_render_social_preview_metadata() -> None:
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
feeds: Response = client.get("/")
if feed_url in feeds.text:
client.post(url="/remove", data={"feed_url": feed_url})
client.post(url="/remove", data={"feed_url": encoded_feed_url(feed_url)})
response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
assert response.status_code == 200, f"Failed to add feed: {response.text}"
assert_social_preview_metadata(
client.get(url="/"),
title="Feeds Dashboard | discord-rss-bot",
description="View configured feeds, broken sources, webhook groups, and delivery status across your Discord RSS bot dashboard.", # noqa: E501
)
assert_social_preview_metadata(
client.get(url="/add"),
title="Add Feed | discord-rss-bot",
description="Add a new RSS or Atom feed and attach it to a Discord webhook for delivery.",
)
assert_social_preview_metadata(
client.get(url="/add_webhook"),
title="Add Webhook | discord-rss-bot",
description="Register a Discord webhook so feeds can post updates into your server or thread.",
)
assert_social_preview_metadata(
client.get(url="/settings"),
title="Settings | discord-rss-bot",
description="Adjust default update intervals, delivery modes, and screenshot layout for feeds managed by your bot.", # noqa: E501
)
assert_social_preview_metadata(
client.get(url="/webhooks"),
title="Webhooks | discord-rss-bot",
description="Manage stored Discord webhooks and inspect the delivery endpoints connected to your feeds.",
)
assert_social_preview_metadata(
client.get(url="/search", params={"query": "a"}),
title="Search: a | discord-rss-bot",
description="Browse search results for a across tracked feed entries and feeds.",
)
feed_response = client.get(url="/feed", params={"feed_url": encoded_feed_url(feed_url)})
assert feed_response.status_code == 200, f"/feed failed: {feed_response.text}"
assert '<meta property="og:title" content="' in feed_response.text
assert "| discord-rss-bot" in feed_response.text
assert (
"Review feed health, delivery settings, filters, webhook attachment, and update intervals for"
in feed_response.text
)
blacklist_response = client.get(url="/blacklist", params={"feed_url": encoded_feed_url(feed_url)})
assert blacklist_response.status_code == 200, f"/blacklist failed: {blacklist_response.text}"
assert '<meta property="og:title" content="Blacklist:' in blacklist_response.text
assert "Block matching entries from" in blacklist_response.text
whitelist_response = client.get(url="/whitelist", params={"feed_url": encoded_feed_url(feed_url)})
assert whitelist_response.status_code == 200, f"/whitelist failed: {whitelist_response.text}"
assert '<meta property="og:title" content="Whitelist:' in whitelist_response.text
assert "Allow only matching entries from" in whitelist_response.text
custom_response = client.get(url="/custom", params={"feed_url": encoded_feed_url(feed_url)})
assert custom_response.status_code == 200, f"/custom failed: {custom_response.text}"
assert '<meta property="og:title" content="Message Template:' in custom_response.text
assert "Customize the plain text Discord message template for" in custom_response.text
embed_response = client.get(url="/embed", params={"feed_url": encoded_feed_url(feed_url)})
assert embed_response.status_code == 200, f"/embed failed: {embed_response.text}"
assert '<meta property="og:title" content="Embed Template:' in embed_response.text
assert "Customize the Discord embed layout, colors, and media for" in embed_response.text
webhook_entries_response = client.get(url="/webhook_entries", params={"webhook_url": webhook_url})
assert webhook_entries_response.status_code == 200, f"/webhook_entries failed: {webhook_entries_response.text}"
assert f'<meta property="og:title" content="{webhook_name} | discord-rss-bot" />' in webhook_entries_response.text
assert (
f"Review webhook settings, attached feeds, and latest entries delivered to {webhook_name}."
in webhook_entries_response.text
)
def test_blacklist_page_uses_live_preview_layout() -> None:
ensure_preview_feed_exists()