diff --git a/discord_rss_bot/custom_message.py b/discord_rss_bot/custom_message.py index 43b16a8..944ba67 100644 --- a/discord_rss_bot/custom_message.py +++ b/discord_rss_bot/custom_message.py @@ -318,7 +318,7 @@ def replace_tags_in_embed(feed: Feed, entry: Entry, reader: Reader) -> CustomEmb entry_updated: str = entry.updated.strftime("%Y-%m-%d %H:%M:%S") if entry.updated else "Never" if embed.title and not embed.author_name and embed.author_url: - msg = "You are using author_url without author_name, but has title set. We will use author_name instead of title when sending the embed to Discord." # noqa: E501 + msg = "You are using author_url without author_name, but has title set. We will use author_name instead of title when sending the embed to Discord." # ruff:ignore[line-too-long] logger.info(msg) embed.author_name = embed.title embed.title = "" @@ -356,6 +356,17 @@ def replace_tags_in_embed(feed: Feed, entry: Entry, reader: Reader) -> CustomEmb for replacement in list_of_replacements: for template, replace_with in replacement.items(): _replace_embed_tags(embed, template, replace_with) + + embed.title = embed.title.replace("\\n", "\n") + embed.description = embed.description.replace("\\n", "\n") + embed.author_name = embed.author_name.replace("\\n", "\n") + embed.author_url = embed.author_url.replace("\\n", "\n") + embed.author_icon_url = embed.author_icon_url.replace("\\n", "\n") + embed.image_url = embed.image_url.replace("\\n", "\n") + embed.thumbnail_url = embed.thumbnail_url.replace("\\n", "\n") + embed.footer_text = embed.footer_text.replace("\\n", "\n") + embed.footer_icon_url = embed.footer_icon_url.replace("\\n", "\n") + return embed @@ -541,7 +552,11 @@ def get_embed(reader: Reader, feed: Feed) -> CustomEmbed: def coerce_embed_bool(value: object) -> bool: - """Normalize stored embed booleans from JSON or form-like values.""" + """Normalize stored embed booleans from JSON or form-like values. + + Returns: + The coerced boolean value. + """ if isinstance(value, bool): return value if isinstance(value, int): diff --git a/discord_rss_bot/templates/embed.html b/discord_rss_bot/templates/embed.html index c2f4595..e94bf24 100644 --- a/discord_rss_bot/templates/embed.html +++ b/discord_rss_bot/templates/embed.html @@ -218,6 +218,12 @@ message, please contact the developer. {% endif %} +
+ +
diff --git a/tests/test_custom_message.py b/tests/test_custom_message.py index b3b8ce5..bd898bc 100644 --- a/tests/test_custom_message.py +++ b/tests/test_custom_message.py @@ -330,6 +330,43 @@ def test_replace_tags_in_embed_uses_last_content_item( assert "New content" in embed.description +@patch("discord_rss_bot.custom_message.get_embed") +def test_replace_tags_in_embed_converts_escaped_newlines( + mock_get_embed: MagicMock, +) -> None: + r"""Verify \\n in embed fields becomes actual newlines, and fields without \\n are unchanged.""" + mock_reader = MagicMock() + mock_get_embed.return_value = CustomEmbed( + title="Line 1\\nLine 2", + description="{{entry_text}}\\ntest\\test2", + author_name="Author\\nSubtitle", + footer_text="Footer\\n\\nMore", + ) + entry_ns: SimpleNamespace = make_entry("

Summary

") + + entry: Entry = typing.cast("Entry", entry_ns) + embed: CustomEmbed = replace_tags_in_embed(entry_ns.feed, entry, reader=mock_reader) + + # Fields with \\n should have actual newlines + assert "\n" in embed.title + assert embed.title == "Line 1\nLine 2" + assert "\n" in embed.author_name + assert embed.author_name == "Author\nSubtitle" + assert "\n" in embed.footer_text + assert embed.footer_text == "Footer\n\nMore" + + # Description combines tag replacement with \\n conversion. + # "{{entry_text}}\\ntest\\\\test2" becomes "Summary text\ntest\\test2". + assert "\n" in embed.description + assert embed.description == "Summary\ntest\\test2" + + # Fields without \\n should be unchanged (backward compat) - none here but + # cover other fields that had no \\n originally + assert "\\n" not in embed.title + assert "\\n" not in embed.author_name + assert "\\n" not in embed.footer_text + + def test_get_custom_message_returns_empty_string_on_value_error() -> None: reader = MagicMock() feed = make_feed()