diff --git a/src/backend/html_markdown.py b/src/backend/html_markdown.py index e690c2fc..8db07257 100644 --- a/src/backend/html_markdown.py +++ b/src/backend/html_markdown.py @@ -3,6 +3,8 @@ """HTML and Markdown processing tools.""" import re +from typing import Dict, Optional +from urllib.parse import quote import html_sanitizer.sanitizer as sanitizer import mistune @@ -162,13 +164,33 @@ class HTMLProcessor: def from_markdown( - self, text: str, inline: bool = False, outgoing: bool = False, + self, + text: str, + inline: bool = False, + outgoing: bool = False, + mentionable_users: Optional[Dict[str, str]] = None, ) -> str: """Return filtered HTML from Markdown text.""" + if mentionable_users: + text = self.markdown_linkify_users(text, mentionable_users) + return self.filter(self._markdown_to_html(text), inline, outgoing) + def markdown_linkify_users(self, text: str, users: Dict[str, str]) -> str: + print(text) + + for user_id, username in users.items(): + repl = rf"\1[{user_id}](https://matrix.to/#/{quote(user_id)})\2" + text = re.sub(rf"(^|\W){user_id}($|\W)", repl, text) + + repl = rf"\1[{username}](https://matrix.to/#/{quote(user_id)})\2" + text = re.sub(rf"(^|\W){username}($|\W)", repl, text) + + return text + + def filter( self, html: str, inline: bool = False, outgoing: bool = False, ) -> str: diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index e36115aa..a1836ac5 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -308,6 +308,15 @@ class MatrixClient(nio.AsyncClient): async def send_text(self, room_id: str, text: str) -> None: """Send a markdown `m.text` or `m.notice` (with `/me`) message .""" + from_md = partial( + HTML.from_markdown, + mentionable_users={ + user_id: member.display_name or user_id + for user_id, member in + self.models[self.user_id, room_id, "members"].items() + }, + ) + escape = False if text.startswith("//") or text.startswith(r"\/"): escape = True @@ -317,13 +326,13 @@ class MatrixClient(nio.AsyncClient): event_type = nio.RoomMessageEmote text = text[len("/me "): ] content = {"body": text, "msgtype": "m.emote"} - to_html = HTML.from_markdown(text, inline=True, outgoing=True) - echo_body = HTML.from_markdown(text, inline=True) + to_html = from_md(text, inline=True, outgoing=True) + echo_body = from_md(text, inline=True) else: event_type = nio.RoomMessageText content = {"body": text, "msgtype": "m.text"} - to_html = HTML.from_markdown(text, outgoing=True) - echo_body = HTML.from_markdown(text) + to_html = from_md(text, outgoing=True) + echo_body = from_md(text) if to_html not in (html.escape(text), f"

{html.escape(text)}

"): content["format"] = "org.matrix.custom.html"