feat(matrix): send markdown as formatted html messages

This commit is contained in:
Alexander Minges
2026-02-10 15:02:03 +01:00
parent 840ef7363f
commit e716c9caac
2 changed files with 44 additions and 1 deletions

View File

@@ -3,6 +3,7 @@ import logging
from typing import Any from typing import Any
from loguru import logger from loguru import logger
from mistune import create_markdown
from nio import ( from nio import (
AsyncClient, AsyncClient,
AsyncClientConfig, AsyncClientConfig,
@@ -21,6 +22,47 @@ from nanobot.config.loader import get_data_dir
LOGGING_STACK_BASE_DEPTH = 2 LOGGING_STACK_BASE_DEPTH = 2
TYPING_NOTICE_TIMEOUT_MS = 30_000 TYPING_NOTICE_TIMEOUT_MS = 30_000
MATRIX_HTML_FORMAT = "org.matrix.custom.html"
MATRIX_MARKDOWN = create_markdown(
escape=True,
plugins=["table", "strikethrough", "task_lists"],
)
def _render_markdown_html(text: str) -> str | None:
"""Render markdown to HTML for Matrix formatted messages."""
try:
formatted = MATRIX_MARKDOWN(text).strip()
except Exception as e:
logger.debug(
"Matrix markdown rendering failed ({}): {}",
type(e).__name__,
str(e),
)
return None
if not formatted:
return None
# Skip formatted_body for plain output (<p>...</p>) to keep payload minimal.
stripped = formatted.strip()
if stripped.startswith("<p>") and stripped.endswith("</p>") and "<p>" not in stripped[3:-4]:
return None
return formatted
def _build_matrix_text_content(text: str) -> dict[str, str]:
"""Build Matrix m.text payload with plaintext fallback and optional HTML."""
content: dict[str, str] = {"msgtype": "m.text", "body": text}
formatted_html = _render_markdown_html(text)
if not formatted_html:
return content
content["format"] = MATRIX_HTML_FORMAT
content["formatted_body"] = formatted_html
return content
class _NioLoguruHandler(logging.Handler): class _NioLoguruHandler(logging.Handler):
@@ -141,7 +183,7 @@ class MatrixChannel(BaseChannel):
await self.client.room_send( await self.client.room_send(
room_id=msg.chat_id, room_id=msg.chat_id,
message_type="m.room.message", message_type="m.room.message",
content={"msgtype": "m.text", "body": msg.content}, content=_build_matrix_text_content(msg.content),
ignore_unverified_devices=True, ignore_unverified_devices=True,
) )
finally: finally:

View File

@@ -43,6 +43,7 @@ dependencies = [
"mcp>=1.26.0,<2.0.0", "mcp>=1.26.0,<2.0.0",
"json-repair>=0.57.0,<1.0.0", "json-repair>=0.57.0,<1.0.0",
"matrix-nio[e2e]>=0.25.2", "matrix-nio[e2e]>=0.25.2",
"mistune>=3.0.0",
] ]
[project.optional-dependencies] [project.optional-dependencies]