From c5ae8543e16907eb10e8ce5896a8f6a5f7f141ae Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 28 Feb 2026 05:23:37 +0000 Subject: [PATCH] Fix Matrix formatting: build HTML directly instead of markdown conversion The markdown library's conversion collapsed blockquote lines into a single paragraph, causing all deal info to render on one line in Matrix clients. Build HTML directly with
tags for reliable line breaks. Also adds html.escape() for proper escaping of titles and prices, and removes the unused markdown dependency. https://claude.ai/code/session_01B7YPGrE3NatkwadCXVjv2j --- gaming_deals_bot/formatter.py | 89 ++++++++++++++--------------------- requirements.txt | 1 - 2 files changed, 35 insertions(+), 55 deletions(-) diff --git a/gaming_deals_bot/formatter.py b/gaming_deals_bot/formatter.py index 876a8e2..626d7a7 100644 --- a/gaming_deals_bot/formatter.py +++ b/gaming_deals_bot/formatter.py @@ -1,25 +1,18 @@ """Message formatting for Matrix deal posts. Produces both a plain-text fallback and HTML formatted body for Matrix messages. +HTML is built directly using the Matrix-supported HTML subset rather than going +through a Markdown-to-HTML converter, which collapses line breaks. """ from datetime import datetime, timezone - -import markdown +from html import escape from .cheapshark import CheapSharkDeal from .currency import format_price from .epic import EpicFreeGame from .itad_deals import ITADDeal -_md = markdown.Markdown() - - -def _render_html(md_text: str) -> str: - """Convert Markdown to HTML, resetting the parser between calls.""" - _md.reset() - return _md.convert(md_text) - def format_deal(deal: CheapSharkDeal, is_historical_low: bool = False) -> tuple[str, str]: """Format a CheapShark deal into (plain_text, html) for Matrix. @@ -32,22 +25,18 @@ def format_deal(deal: CheapSharkDeal, is_historical_low: bool = False) -> tuple[ sale_multi = format_price(sale_price) normal_display = format_price(normal_price) + title = escape(deal.title) - lines = [ - f"**🎮 [DEAL] {deal.title}**", - f"> {discount}% off on {deal.store_name} ~~{normal_display}~~", - f"> 💰 **{sale_multi}**", + html_lines = [ + f"🎮 [DEAL] {title}", + f"{discount}% off on {escape(deal.store_name)} {escape(normal_display)}", + f"💰 {escape(sale_multi)}", ] - if is_historical_low: - lines.append("> 🏆 _All-time low!_") + html_lines.append("🏆 All-time low!") + html_lines.append(f'🔗 View Deal') + html = "
\n".join(html_lines) - lines.append(f"> 🔗 [View Deal]({deal.deal_url})") - - md_text = "\n".join(lines) - html = _render_html(md_text) - - # Plain text fallback — strip markdown syntax plain_lines = [ f"🎮 [DEAL] {deal.title}", f" {discount}% off on {deal.store_name} (was {normal_display})", @@ -56,7 +45,6 @@ def format_deal(deal: CheapSharkDeal, is_historical_low: bool = False) -> tuple[ if is_historical_low: plain_lines.append(" 🏆 All-time low!") plain_lines.append(f" 🔗 {deal.deal_url}") - plain_text = "\n".join(plain_lines) return plain_text, html @@ -69,20 +57,17 @@ def format_itad_deal(deal: ITADDeal) -> tuple[str, str]: """ sale_multi = format_price(deal.sale_price) normal_display = format_price(deal.normal_price) + title = escape(deal.title) - lines = [ - f"**🎮 [DEAL] {deal.title}**", - f"> {deal.discount}% off on {deal.shop_name} ~~{normal_display}~~", - f"> 💰 **{sale_multi}**", + html_lines = [ + f"🎮 [DEAL] {title}", + f"{deal.discount}% off on {escape(deal.shop_name)} {escape(normal_display)}", + f"💰 {escape(sale_multi)}", ] - if deal.is_historical_low: - lines.append("> 🏆 _All-time low!_") - - lines.append(f"> 🔗 [View Deal]({deal.url})") - - md_text = "\n".join(lines) - html = _render_html(md_text) + html_lines.append("🏆 All-time low!") + html_lines.append(f'🔗 View Deal') + html = "
\n".join(html_lines) plain_lines = [ f"🎮 [DEAL] {deal.title}", @@ -92,7 +77,6 @@ def format_itad_deal(deal: ITADDeal) -> tuple[str, str]: if deal.is_historical_low: plain_lines.append(" 🏆 All-time low!") plain_lines.append(f" 🔗 {deal.url}") - plain_text = "\n".join(plain_lines) return plain_text, html @@ -103,26 +87,25 @@ def format_epic_free(game: EpicFreeGame) -> tuple[str, str]: Returns (body, formatted_body). """ - lines = [ - f"**🆓 [FREE] {game.title}**", - "> Free on Epic Games Store", + title = escape(game.title) + + html_lines = [ + f"🆓 [FREE] {title}", + "Free on Epic Games Store", ] if game.end_date: try: end_dt = datetime.fromisoformat(game.end_date.replace("Z", "+00:00")) date_str = end_dt.strftime("%B %-d") - lines.append(f"> 📅 _Free until {date_str}_") + html_lines.append(f"📅 Free until {date_str}") except (ValueError, TypeError): pass if game.url: - lines.append(f"> 🔗 [Claim Now]({game.url})") + html_lines.append(f'🔗 Claim Now') + html = "
\n".join(html_lines) - md_text = "\n".join(lines) - html = _render_html(md_text) - - # Plain text fallback plain_lines = [ f"🆓 [FREE] {game.title}", " Free on Epic Games Store", @@ -136,7 +119,6 @@ def format_epic_free(game: EpicFreeGame) -> tuple[str, str]: pass if game.url: plain_lines.append(f" 🔗 {game.url}") - plain_text = "\n".join(plain_lines) return plain_text, html @@ -144,24 +126,24 @@ def format_epic_free(game: EpicFreeGame) -> tuple[str, str]: def format_epic_upcoming(game: EpicFreeGame) -> tuple[str, str]: """Format an upcoming Epic free game into (plain_text, html) for Matrix.""" - lines = [ - f"**📢 [UPCOMING FREE] {game.title}**", - "> Coming soon — Free on Epic Games Store", + title = escape(game.title) + + html_lines = [ + f"📢 [UPCOMING FREE] {title}", + "Coming soon — Free on Epic Games Store", ] if game.end_date: try: end_dt = datetime.fromisoformat(game.end_date.replace("Z", "+00:00")) date_str = end_dt.strftime("%B %-d") - lines.append(f"> 📅 _Free until {date_str}_") + html_lines.append(f"📅 Free until {date_str}") except (ValueError, TypeError): pass if game.url: - lines.append(f"> 🔗 [Store Page]({game.url})") - - md_text = "\n".join(lines) - html = _render_html(md_text) + html_lines.append(f'🔗 Store Page') + html = "
\n".join(html_lines) plain_lines = [ f"📢 [UPCOMING FREE] {game.title}", @@ -169,7 +151,6 @@ def format_epic_upcoming(game: EpicFreeGame) -> tuple[str, str]: ] if game.url: plain_lines.append(f" 🔗 {game.url}") - plain_text = "\n".join(plain_lines) return plain_text, html diff --git a/requirements.txt b/requirements.txt index f8562a2..944872d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,4 @@ matrix-nio>=0.21.0 httpx>=0.25.0 aiosqlite>=0.19.0 apscheduler>=3.10.0 -markdown>=3.5.0 python-dotenv>=1.0.0