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