diff --git a/discord_rss_bot/custom_message.py b/discord_rss_bot/custom_message.py index ca7d988..4a9b9e4 100644 --- a/discord_rss_bot/custom_message.py +++ b/discord_rss_bot/custom_message.py @@ -1,5 +1,6 @@ from __future__ import annotations +import html import json import logging from dataclasses import dataclass @@ -7,13 +8,16 @@ from typing import TYPE_CHECKING from bs4 import BeautifulSoup from bs4 import Tag +from markupsafe import Markup from discord_rss_bot.extensions import run_extensions from discord_rss_bot.extensions.steam import fix_steam_image_url +from discord_rss_bot.extensions.steam import get_feed_display_title from discord_rss_bot.html_format import format_entry_html_for_discord from discord_rss_bot.is_url_valid import is_url_valid if TYPE_CHECKING: + from collections.abc import Mapping from collections.abc import Sequence from reader import Content @@ -105,7 +109,7 @@ def replace_tags_in_text_message(entry: Entry, reader: Reader) -> str: {"{{feed_last_updated}}": feed_last_updated}, {"{{feed_link}}": feed.link or ""}, {"{{feed_subtitle}}": feed.subtitle or ""}, - {"{{feed_title}}": feed.title or ""}, + {"{{feed_title}}": get_feed_display_title(feed, reader)}, {"{{feed_updated}}": feed_updated}, {"{{feed_updates_enabled}}": str(feed.updates_enabled) or ""}, {"{{feed_url}}": feed.url or ""}, @@ -278,7 +282,7 @@ def replace_tags_in_embed(feed: Feed, entry: Entry, reader: Reader) -> CustomEmb {"{{feed_last_updated}}": feed_last_updated or ""}, {"{{feed_link}}": feed.link or ""}, {"{{feed_subtitle}}": feed.subtitle or ""}, - {"{{feed_title}}": feed.title or ""}, + {"{{feed_title}}": get_feed_display_title(feed, reader)}, {"{{feed_updated}}": feed_updated or ""}, {"{{feed_updates_enabled}}": "True" if feed.updates_enabled else "False"}, {"{{feed_url}}": feed.url or ""}, @@ -344,6 +348,143 @@ def _replace_embed_tags(embed: CustomEmbed, template: str, replace_with: str) -> embed.username = try_to_replace(embed.username, template, replace_with) +def _tag_reference_item(tag: str, value: object) -> str: + """Return one reference row for the template-variable preview list. + + Args: + tag: The template variable name without braces, e.g. ``feed_title``. + value: The preview value to display next to the tag. + + Returns: + An HTML ``
`` and the escaped value.
+ """
+ escaped_value: str = html.escape(str(value), quote=True)
+ return f"{{{{{tag}}}}} {escaped_value} "
+
+
+def _tag_reference_value(obj: object, name: str) -> object:
+ """Read ``obj.name``, returning an empty string when the attribute is missing.
+
+ Mirrors Jinja's lenient attribute lookup so the reference list also works
+ with the minimal stubs used in tests.
+
+ Args:
+ obj: The object to read the attribute from.
+ name: The attribute name.
+
+ Returns:
+ The attribute value, or an empty string when it is missing.
+ """
+ return getattr(obj, name, "")
+
+
+def render_tag_reference_html(
+ *,
+ feed: Feed,
+ entry: Entry | None,
+ feed_title: str,
+ first_image: str,
+ extension_variables: Sequence[str] = (),
+ extension_values: Mapping[str, str] | None = None,
+) -> Markup:
+ """Render the template-variable reference list for the custom/embed pages.
+
+ The list used to be hardcoded in ``custom.html`` and ``embed.html`` with
+ many ``{% raw %}`` blocks, which broke whenever a formatter touched the
+ templates. Building the markup here keeps the ``{{ ... }}`` tags intact
+ because Jinja does not re-parse the returned string.
+
+ Args:
+ feed: The feed whose values are previewed.
+ entry: The first entry to preview, or None when the feed has no entries.
+ feed_title: The resolved display title for the feed (``{{feed_title}}``).
+ first_image: The first image URL found in the entry (``{{image_1}}``).
+ extension_variables: Names of the enabled extension variables.
+ extension_values: Values produced by the enabled extensions.
+
+ Returns:
+ A ```` list of reference rows, safe to embed directly in HTML.
+ """
+ if extension_values is None:
+ extension_values = {}
+
+ rows: list[str] = ['', "
"]
+ rows.extend(
+ [
+ _tag_reference_item("feed_author", _tag_reference_value(feed, "authors_str")),
+ _tag_reference_item("feed_added", _tag_reference_value(feed, "added")),
+ _tag_reference_item("feed_last_exception", _tag_reference_value(feed, "last_exception")),
+ _tag_reference_item("feed_last_updated", _tag_reference_value(feed, "last_updated")),
+ _tag_reference_item("feed_link", _tag_reference_value(feed, "link")),
+ _tag_reference_item("feed_subtitle", _tag_reference_value(feed, "subtitle")),
+ _tag_reference_item("feed_title", feed_title),
+ _tag_reference_item("feed_updated", _tag_reference_value(feed, "updated")),
+ _tag_reference_item("feed_updates_enabled", _tag_reference_value(feed, "updates_enabled")),
+ _tag_reference_item("feed_url", _tag_reference_value(feed, "url")),
+ _tag_reference_item("feed_user_title", _tag_reference_value(feed, "user_title")),
+ _tag_reference_item("feed_version", _tag_reference_value(feed, "version")),
+ ],
+ )
+ rows.append("
")
+
+ if entry is not None:
+ content_html: str = entry.content[0].value if entry.content else ""
+ summary: str = entry.summary or ""
+ entry_content: str = format_entry_html_for_discord(content_html) if entry.content else ""
+ entry_summary: str = format_entry_html_for_discord(summary) if entry.summary else ""
+ entry_text: str = entry_content if entry.content else entry_summary
+
+ rows.extend(
+ [
+ _tag_reference_item("entry_added", entry.added),
+ _tag_reference_item("entry_author", entry.authors_str or ""),
+ ],
+ )
+ if entry.content:
+ rows.extend((
+ _tag_reference_item("entry_content", entry_content),
+ _tag_reference_item("entry_content_raw", content_html),
+ ))
+ rows.extend(
+ [
+ _tag_reference_item("entry_id", entry.id),
+ _tag_reference_item("entry_important", entry.important),
+ _tag_reference_item("entry_link", entry.link or ""),
+ _tag_reference_item("entry_published", entry.published),
+ _tag_reference_item("entry_read", entry.read),
+ _tag_reference_item("entry_read_modified", entry.read_modified),
+ ],
+ )
+ if entry.summary:
+ rows.extend((
+ _tag_reference_item("entry_summary", entry_summary),
+ _tag_reference_item("entry_summary_raw", summary),
+ ))
+ rows.extend(
+ [
+ _tag_reference_item("entry_title", entry.title or ""),
+ _tag_reference_item("entry_text", entry_text),
+ _tag_reference_item("entry_updated", entry.updated),
+ ],
+ )
+ rows.extend(("
", _tag_reference_item("image_1", first_image)))
+
+ if extension_variables:
+ rows.extend(("
", "- Extension variables (from enabled extensions):
"))
+ for var_name in extension_variables:
+ var_value: str = extension_values.get(var_name, "") or "(empty)"
+ rows.append(_tag_reference_item(var_name, var_value))
+ else:
+ rows.append(
+ "Something went wrong, there was no entry found. If this feed has entries and you still see this "
+ "message, please contact the developer.",
+ )
+
+ rows.append("
")
+ # Safe: every dynamic value is HTML-escaped by _tag_reference_item.
+ return Markup("\n".join(rows)) # ruff: ignore[unsafe-markup-use]
+
+
def get_custom_message(reader: Reader, feed: Feed) -> str:
"""Get custom_message tag from feed.
diff --git a/discord_rss_bot/extensions/steam.py b/discord_rss_bot/extensions/steam.py
index dccf1c3..fd148b8 100644
--- a/discord_rss_bot/extensions/steam.py
+++ b/discord_rss_bot/extensions/steam.py
@@ -8,6 +8,11 @@ It also rewrites broken Steam CDN image URLs: some news feeds reference
localized image names such as ``.../english.png`` that return a 404, and
the working URL omits the language segment (``.../.png``).
+Steam feeds' ``{{feed_title}}`` (and web UI display names) use the game
+name (e.g. "Dota") instead of the raw feed title (e.g. "570 RSS Feed").
+The name is fetched once from the Steam Store API and cached in the reader
+DB per feed, so no repeated network requests are needed.
+
Usage:
1. Enable ``steam`` on the feed's Extensions page.
2. In embed settings, set Thumbnail URL to ``{{steam_thumbnail_url}}``.
@@ -19,6 +24,7 @@ import hashlib
import json
import logging
import re
+import time
from pathlib import Path
from typing import TYPE_CHECKING
from typing import ClassVar
@@ -28,6 +34,9 @@ from urllib.parse import parse_qs
from urllib.parse import urlparse
from urllib.parse import urlunsplit
+import httpx2
+from httpx2 import HTTPError
+from httpx2 import Response
from reader import ReaderError
from discord_rss_bot.extensions.base import FeedExtension
@@ -258,6 +267,172 @@ def fix_steam_image_urls_in_payload(value: JsonValue) -> JsonValue:
return value
+#: Reader tag under which the Steam game name is cached per feed. Populated
+#: once per feed so title rendering never needs a second network request.
+STEAM_GAME_NAME_TAG: str = "steam_game_name"
+
+#: Steam Store API endpoint used to resolve app IDs to game names.
+_STEAM_APPDETAILS_URL: str = "https://store.steampowered.com/api/appdetails"
+
+#: How long to wait before retrying a failed game-name lookup for the same app.
+_STEAM_GAME_NAME_RETRY_COOLDOWN: float = 60.0 * 60.0
+
+#: Last lookup attempt time per app ID. Used so a repeatedly failing feed
+#: does not trigger a network request on every single message.
+_steam_game_name_attempts: dict[str, float] = {}
+
+
+def get_steam_game_name(app_id: str) -> str | None: # ruff:ignore[too-many-return-statements]
+ """Fetch the game name for *app_id* from the Steam Store API.
+
+ Args:
+ app_id: The Steam application ID.
+
+ Returns:
+ The game name, or ``None`` if it could not be determined.
+ """
+ try:
+ response: Response = httpx2.get(
+ _STEAM_APPDETAILS_URL,
+ params={"appids": app_id, "l": "english"},
+ timeout=10.0,
+ )
+ response.raise_for_status()
+ except (HTTPError, OSError) as exc:
+ logger.warning("Failed to fetch Steam game name for app %s: %s", app_id, exc)
+ return None
+
+ try:
+ data: object = response.json()
+ except ValueError as exc:
+ logger.warning("Invalid JSON from Steam Store API for app %s: %s", app_id, exc)
+ return None
+
+ if not isinstance(data, dict):
+ return None
+ details: object = data.get(app_id)
+ if not isinstance(details, dict) or not details.get("success"):
+ logger.warning("Steam Store API returned no details for app %s", app_id)
+ return None
+ app_data: object = details.get("data")
+ if not isinstance(app_data, dict):
+ return None
+ name: object = app_data.get("name")
+ if isinstance(name, str) and name.strip():
+ return name.strip()
+ return None
+
+
+def get_cached_steam_game_name(reader: Reader, feed: Feed | str) -> str | None:
+ """Return the Steam game name cached in the reader DB for *feed*, if any.
+
+ Args:
+ reader: The reader instance.
+ feed: The feed (or feed URL) to look up.
+
+ Returns:
+ The cached game name, or ``None`` when nothing is cached.
+ """
+ try:
+ cached = reader.get_tag(feed, STEAM_GAME_NAME_TAG, None)
+ except ReaderError:
+ logger.exception("Failed to read Steam game name tag for %s", feed)
+ return None
+ if isinstance(cached, str) and cached.strip():
+ return cached.strip()
+ return None
+
+
+def set_steam_game_name(reader: Reader, feed: Feed | str, game_name: str) -> None:
+ """Cache *game_name* for *feed* in the reader DB.
+
+ Args:
+ reader: The reader instance.
+ feed: The feed (or feed URL) to store the name for.
+ game_name: The game name to cache.
+ """
+ try:
+ reader.set_tag(feed, STEAM_GAME_NAME_TAG, game_name) # pyright: ignore[reportArgumentType]
+ except ReaderError:
+ logger.exception("Failed to cache Steam game name for %s", feed)
+
+
+def get_feed_display_title(feed: Feed, reader: Reader) -> str:
+ """Return the display title for a feed.
+
+ For Steam feeds, uses the game name (e.g. "Dota") instead of the raw feed
+ title (e.g. "570 RSS Feed"). The name is read from the reader DB cache;
+ when the cache is empty it is fetched from the Steam Store API once and
+ then cached, so title rendering never makes "unnecessary network requests"
+ for feeds that have already been resolved. Falls back to the feed's own
+ title when the game name cannot be determined. Non-Steam feeds are
+ returned with their title unchanged.
+
+ Args:
+ feed: The feed to render a title for.
+ reader: The reader instance.
+
+ Returns:
+ The display title for *feed*.
+ """
+ feed_url: str = getattr(feed, "url", "") or ""
+ fallback_title: str = getattr(feed, "title", "") or ""
+ if not is_steam_url(feed_url):
+ return fallback_title
+
+ app_id: str | None = extract_app_id(feed_url)
+ if not app_id:
+ return fallback_title
+
+ cached: str | None = get_cached_steam_game_name(reader, feed)
+ if cached:
+ return cached
+
+ # Don't hammer the API for app IDs whose lookup recently failed.
+ now: float = time.monotonic()
+ last_attempt: float = _steam_game_name_attempts.get(app_id, 0.0)
+ if now - last_attempt < _STEAM_GAME_NAME_RETRY_COOLDOWN:
+ return fallback_title
+
+ _steam_game_name_attempts[app_id] = now
+ game_name: str | None = get_steam_game_name(app_id)
+ if game_name:
+ set_steam_game_name(reader, feed, game_name)
+ return game_name
+ return fallback_title
+
+
+def get_cached_feed_display_title(feed: Feed, reader: Reader) -> str:
+ """Return the display title for a feed using only cached data.
+
+ For Steam feeds, uses the cached game name when available, otherwise the
+ feed's own title. Unlike :func:`get_feed_display_title`, this never
+ performs network requests or writes to the reader — it only reads the
+ cache. Meant for read-only render paths (e.g. web UI pages) that must not
+ block on the Steam Store API.
+
+ Args:
+ feed: The feed to render a title for.
+ reader: The reader instance.
+
+ Returns:
+ The display title for *feed* using only cached data.
+ """
+ feed_url: str = getattr(feed, "url", "") or ""
+ fallback_title: str = getattr(feed, "title", "") or ""
+ if not is_steam_url(feed_url):
+ return fallback_title
+
+ app_id: str | None = extract_app_id(feed_url)
+ if not app_id:
+ return fallback_title
+
+ cached: str | None = get_cached_steam_game_name(reader, feed)
+ if cached:
+ return cached
+ return fallback_title
+
+
def _try_read_icon_file(app_id: str) -> WebhookFile | None:
"""Read a Steam game icon from disk, returning a ``WebhookFile`` or ``None``.
diff --git a/discord_rss_bot/feeds.py b/discord_rss_bot/feeds.py
index 5d6da2f..04460a2 100644
--- a/discord_rss_bot/feeds.py
+++ b/discord_rss_bot/feeds.py
@@ -57,6 +57,8 @@ from discord_rss_bot.custom_message import replace_tags_in_embed
from discord_rss_bot.custom_message import replace_tags_in_text_message
from discord_rss_bot.extensions import auto_enable_extensions_for_feed
from discord_rss_bot.extensions import run_modify_webhook
+from discord_rss_bot.extensions.steam import get_cached_feed_display_title
+from discord_rss_bot.extensions.steam import get_feed_display_title
from discord_rss_bot.filter.evaluator import get_entry_filter_decision_from_reader
from discord_rss_bot.is_url_valid import is_url_valid
from discord_rss_bot.settings import default_custom_embed
@@ -141,12 +143,14 @@ MESSAGE_PAYLOAD_KEYS: tuple[str, ...] = (
)
-def get_feed_display_name(feed: Feed) -> str:
+def get_feed_display_name(feed: Feed, reader: Reader | None = None) -> str:
"""Return a human-readable display name for a feed.
For YouTube feeds whose title is just "Videos", shows
- ``"{author}'s Videos"`` instead. Other feeds return the title
- as-is.
+ ``"{author}'s Videos"`` instead. For Steam feeds, shows the game name
+ (e.g. "Dota") instead of the raw feed title (e.g. "570 RSS Feed") when
+ *reader* is provided and the name is cached. Other feeds return the
+ title as-is.
"""
url: str = feed.url or ""
title: str = feed.title or ""
@@ -156,6 +160,14 @@ def get_feed_display_name(feed: Feed) -> str:
if author:
return f"{author}'s Videos"
+ if reader is not None:
+ steam_title: str = get_cached_feed_display_title(feed, reader)
+ if steam_title and steam_title != title:
+ try:
+ return html.unescape(steam_title)
+ except (TypeError, AttributeError):
+ return steam_title
+
try:
return html.unescape(title) if title else url
except (TypeError, AttributeError):
@@ -648,7 +660,7 @@ def upsert_sent_webhook_record(
delivery_mode: DeliveryMode = get_entry_delivery_mode(reader, entry)
record: SentWebhookRecord = {
"feed_url": entry.feed.url,
- "feed_title": entry.feed.title or "",
+ "feed_title": get_feed_display_title(entry.feed, reader),
"entry_id": entry.id,
"entry_title": entry.title or "",
"entry_link": entry.link or "",
@@ -878,9 +890,9 @@ def apply_feed_webhook_identity(webhook: DiscordWebhook, entry: Entry, reader: R
avatar_url: str = get_validated_message_avatar_url(reader, feed)
if not username:
- # Fall back to feed author or title so the webhook name is
+ # Fall back to feed author or display title so the webhook name is
# identifiable rather than the generic Discord webhook default.
- feed_author: str = feed.authors_str or feed.title or ""
+ feed_author: str = feed.authors_str or get_feed_display_title(feed, reader) or ""
if feed_author:
username = html.unescape(feed_author)[:80]
@@ -1043,7 +1055,7 @@ def update_sent_webhook_record_for_entry(
return (
{
**record,
- "feed_title": entry.feed.title or "",
+ "feed_title": get_feed_display_title(entry.feed, reader),
"entry_title": entry.title or "",
"entry_link": entry.link or "",
"entry_updated": get_entry_timestamp(entry.updated),
diff --git a/discord_rss_bot/main.py b/discord_rss_bot/main.py
index ced82e6..07d548c 100644
--- a/discord_rss_bot/main.py
+++ b/discord_rss_bot/main.py
@@ -58,11 +58,14 @@ from discord_rss_bot.custom_message import get_embed
from discord_rss_bot.custom_message import get_first_image
from discord_rss_bot.custom_message import get_message_avatar_url
from discord_rss_bot.custom_message import get_message_username
+from discord_rss_bot.custom_message import render_tag_reference_html
from discord_rss_bot.custom_message import replace_tags_in_text_message
from discord_rss_bot.custom_message import save_embed
from discord_rss_bot.extensions import FeedExtension as FeedExtensionABC
from discord_rss_bot.extensions import get_registry as get_extension_registry
from discord_rss_bot.extensions import run_extensions
+from discord_rss_bot.extensions.steam import get_cached_feed_display_title
+from discord_rss_bot.extensions.steam import get_feed_display_title
from discord_rss_bot.extensions.steam import is_steam_url as is_steam_feed_url
from discord_rss_bot.extensions.storage import get_enabled_extensions_for_feed as get_enabled_extensions
from discord_rss_bot.extensions.storage import set_enabled_extensions_for_feed as set_enabled_extensions
@@ -295,6 +298,7 @@ templates.env.filters["relative_time"] = relative_time
templates.env.filters["feed_display_name"] = get_feed_display_name
templates.env.globals["get_backup_path"] = get_backup_path # pyright: ignore[reportArgumentType]
templates.env.globals["has_webhooks"] = has_webhooks # pyright: ignore[reportArgumentType]
+templates.env.globals["render_tag_reference_html"] = render_tag_reference_html # pyright: ignore[reportArgumentType]
@app.get("/export_opml")
@@ -839,6 +843,7 @@ async def get_whitelist(
context = {
"request": request,
"feed": feed,
+ "feed_title": get_feed_display_title(feed, reader),
**build_filter_form_context("whitelist", get_filter_values_from_reader(reader, feed, "whitelist")),
**build_filter_preview_context(reader, feed, "whitelist"),
}
@@ -970,6 +975,7 @@ async def get_blacklist(
context = {
"request": request,
"feed": feed,
+ "feed_title": get_feed_display_title(feed, reader),
**build_filter_form_context("blacklist", get_filter_values_from_reader(reader, feed, "blacklist")),
**build_filter_preview_context(reader, feed, "blacklist"),
}
@@ -1442,6 +1448,7 @@ async def get_custom(
context: dict[str, object] = {
"request": request,
"feed": feed,
+ "feed_title": get_feed_display_title(feed, reader),
"custom_message": get_custom_message(reader, feed),
"message_username": get_message_username(reader, feed),
"message_avatar_url": get_message_avatar_url(reader, feed),
@@ -1501,6 +1508,7 @@ async def get_embed_page(
context: dict[str, object] = {
"request": request,
"feed": feed,
+ "feed_title": get_feed_display_title(feed, reader),
"title": embed.title,
"description": embed.description,
"color": embed.color,
@@ -1633,6 +1641,7 @@ async def get_extensions(
context: dict[str, object] = {
"request": request,
"feed": feed,
+ "feed_title": get_feed_display_title(feed, reader),
"discovered_extensions": registry,
"enabled_extensions": enabled,
"extensions_dir": extensions_dir,
@@ -2185,7 +2194,9 @@ async def get_feed( # ruff:ignore[complex-structure, too-many-branches, too-man
context = {
"request": request,
+ "reader": reader,
"feed": feed,
+ "feed_title": get_feed_display_title(feed, reader),
"entries": current_entries,
"feed_counts": reader.get_feed_counts(feed=clean_feed_url),
"html": html,
@@ -2253,7 +2264,9 @@ async def get_feed( # ruff:ignore[complex-structure, too-many-branches, too-man
context = {
"request": request,
+ "reader": reader,
"feed": feed,
+ "feed_title": get_feed_display_title(feed, reader),
"entries": entries,
"feed_counts": reader.get_feed_counts(feed=clean_feed_url),
"html": html,
@@ -2343,7 +2356,7 @@ def create_html_for_feed( # ruff:ignore[complex-structure, too-many-locals]
feed_link: str = ""
if not current_feed_url or source_feed_url != current_feed_url:
encoded_feed_url: str = urllib.parse.quote(source_feed_url)
- feed_title: str = entry.feed.title if hasattr(entry.feed, "title") and entry.feed.title else source_feed_url
+ feed_title: str = get_cached_feed_display_title(entry.feed, reader) or source_feed_url
feed_link = (
f"{feed_title}
"
@@ -2582,7 +2595,9 @@ async def get_sent_webhooks(
webhook_names: dict[str, str] = {
hook.get("url", ""): hook.get("name", "") for hook in webhooks if isinstance(hook, dict)
}
- feed_titles: dict[str, str] = {feed.url: (feed.title or feed.url) for feed in reader.get_feeds()}
+ feed_titles: dict[str, str] = {
+ feed.url: (get_cached_feed_display_title(feed, reader) or feed.url) for feed in reader.get_feeds()
+ }
context = {
"request": request,
@@ -2653,6 +2668,7 @@ def make_context_index(request: Request, message: str = "", reader: Reader | Non
return {
"request": request,
+ "reader": effective_reader,
"feeds": feed_list,
"feed_count": effective_reader.get_feed_counts(),
"entry_count": effective_reader.get_entry_counts(),
diff --git a/discord_rss_bot/templates/blacklist.html b/discord_rss_bot/templates/blacklist.html
index b9c2270..66b1f28 100644
--- a/discord_rss_bot/templates/blacklist.html
+++ b/discord_rss_bot/templates/blacklist.html
@@ -1,9 +1,9 @@
{% extends "base.html" %}
{% block title %}
- Blacklist: {{ feed.title if feed.title else feed.url }} | discord-rss-bot
+ 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.
+ Block matching entries from {{ feed_title if feed_title else feed.url }} before they are delivered to Discord.
{% endblock description %}
{% block content %}
diff --git a/discord_rss_bot/templates/custom.html b/discord_rss_bot/templates/custom.html
index 4b91e18..e11f69d 100644
--- a/discord_rss_bot/templates/custom.html
+++ b/discord_rss_bot/templates/custom.html
@@ -1,277 +1,65 @@
{% extends "base.html" %}
-{% 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 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 %}
-
-