From 294bd887ba1677a3a509ffafc6ddebd41badd190 Mon Sep 17 00:00:00 2001 From: Tim Clifford Date: Sat, 27 Jan 2024 17:36:39 +0000 Subject: [PATCH] Catch some reaction failure modes, make spoilers black bars --- src/backend/html_markdown.py | 36 +++++++++++++++----- src/backend/matrix_client.py | 66 +++++++++++++++++++++++++++++------- 2 files changed, 81 insertions(+), 21 deletions(-) diff --git a/src/backend/html_markdown.py b/src/backend/html_markdown.py index 18cc5091..95fa8959 100644 --- a/src/backend/html_markdown.py +++ b/src/backend/html_markdown.py @@ -138,13 +138,6 @@ class HTMLProcessor: re.MULTILINE, ) - spoiler_regex = re.compile( - r"(]+data-mx-spoiler[^>]*>)" - r"(.*?)" - r"()", - re.MULTILINE, - ) - extra_newlines_regex = re.compile(r"\n(\n*)") @@ -230,9 +223,28 @@ class HTMLProcessor: # Client-side modifications + # re-parsing, will sanitize again but allowing style + tree = etree.fromstring( + html, parser=etree.HTMLParser(encoding="utf-8"), + ) + + for span_tag in tree.iterfind(".//span[@data-mx-spoiler]"): + # if there are sub-elements, their styles also need to be set or + # background-color doesn't seem to apply + for tag in span_tag.iter(): + tag.set( + "style", + "color: black !important; background-color: black !important;" + + (tag.get("style") or "") + ) + + html = etree.tostring(tree, encoding="utf-8", method="html").decode() + + html = Sanitizer(self.sanitize_settings( + inline, outgoing, mentions, extra_attributes={"style"} + )).sanitize(html).rstrip("\n") + html = self.quote_regex.sub(r'\1\2\3', html) - html = self.spoiler_regex.sub( - r'\1\2\3', html) if not inline: return html @@ -247,6 +259,7 @@ class HTMLProcessor: inline: bool = False, outgoing: bool = False, display_name_mentions: Optional[Dict[str, str]] = None, + extra_attributes: set = set(), ) -> dict: """Return an html_sanitizer configuration.""" @@ -269,6 +282,11 @@ class HTMLProcessor: }, }} + for key in inlines_attributes: + inlines_attributes[key] |= extra_attributes + for key in attributes: + attributes[key] |= extra_attributes + username_link_regexes = [re.compile(r) for r in [ rf"(?{re.escape(name or user_id)})(?!\w)(?P)" for user_id, name in (display_name_mentions or {}).items() diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index 1f74346f..9831d6eb 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -771,7 +771,7 @@ class MatrixClient(nio.AsyncClient): ) -> None: if reply_to_event_id is None or reply_to_event_id == "": await self.send_fake_notice( - room_id, "please reply to a message to react to it 🙃") + room_id, "Please reply to a message to react to it") else: reaction = emoji.emojize(text, language="alias") await self.send_reaction(room_id, reaction, reply_to_event_id) @@ -792,9 +792,19 @@ class MatrixClient(nio.AsyncClient): to_html = from_md(text, outgoing=True) echo_body = from_md(text) - to_html = f"{to_html}" - echo_body = f"{echo_body}" + if to_html.startswith("

") and to_html.endswith("

"): + # we want to make sure the is inside the

, otherwise the + # black bar will be too wide + inner_html = to_html[len('

'):-len('

')] + to_html = f"

{inner_html}

" + else: + to_html = f"{to_html}" + if echo_body.startswith("

") and echo_body.endswith("

"): + inner_html = echo_body[len('

'):-len('

')] + echo_body = f"

{inner_html}

" + else: + echo_body = f"{echo_body}" await self._send_text( room_id, @@ -816,7 +826,7 @@ class MatrixClient(nio.AsyncClient): if reply_to_event_id is None or reply_to_event_id == "": await self.send_fake_notice( room_id, - "Please reply to a message with /unspoiler to unspoiler it 🙃", + "Please reply to a message with /unspoiler to unspoiler it", ) else: spoiler_event: Event = \ @@ -835,16 +845,31 @@ class MatrixClient(nio.AsyncClient): async def send_reaction( self, - room_id: str, - key: str, + room_id: str, + key: str, reacts_to: str, ) -> None: # local event id in model isn't necessarily the actual event id - reacts_to_event_id = self.models[ - self.user_id, room_id, "events"][reacts_to].event_id + reacts_to_event = self.models[ + self.user_id, room_id, "events"][reacts_to] - item_uuid = uuid4() + reacts_to_event_id = reacts_to_event.event_id + + if self.user_id in reacts_to_event.reactions.get(key, {}).get('users', []): + await self.send_fake_notice( + room_id, + "Can't send the same reaction more than once", + ) + return + elif reacts_to_event_id.startswith("echo-"): + await self.send_fake_notice( + room_id, + "Can't react to that, it's not a real event", + ) + return + + item_uuid = uuid4() content: Dict[str, Any] = { "m.relates_to": { @@ -858,8 +883,25 @@ class MatrixClient(nio.AsyncClient): content[f"{__reverse_dns__}.transaction_id"] = str(tx_id) await self.pause_while_offline() - await self._send_message( - room_id, content, item_uuid, message_type = "m.reaction") + try: + await self._send_message( + room_id, content, item_uuid, message_type = "m.reaction") + except MatrixError as err: + if err.m_code == "M_DUPLICATE_ANNOTATION": + # potentially possible if the new reaction is + # sent before the existing reaction is loaded + await self.send_fake_notice( + room_id, + "Can't send the same reaction more than once", + ) + return + if err.m_code == "M_UNKNOWN": + await self.send_fake_notice( + room_id, + "Failed to send reaction. Has the event you are reacting to fully sent yet?", + ) + else: + raise err # only update the UI after the reaction is sent, to not be misleading await self._register_reaction( @@ -1222,7 +1264,7 @@ class MatrixClient(nio.AsyncClient): event = Event( id = f"echo-{transaction_id}", - event_id = "", + event_id = f"echo-{transaction_id}" if fake_event else "", event_type = event_type, date = datetime.now(), sender_id = self.user_id,