Catch some reaction failure modes, make spoilers black bars

This commit is contained in:
Tim Clifford 2024-01-27 17:36:39 +00:00 committed by Maze
parent 8eb1afb91c
commit 294bd887ba
2 changed files with 81 additions and 21 deletions

View File

@ -138,13 +138,6 @@ class HTMLProcessor:
re.MULTILINE, re.MULTILINE,
) )
spoiler_regex = re.compile(
r"(<span[^>]+data-mx-spoiler[^>]*>)"
r"(.*?)"
r"(</?span>)",
re.MULTILINE,
)
extra_newlines_regex = re.compile(r"\n(\n*)") extra_newlines_regex = re.compile(r"\n(\n*)")
@ -230,9 +223,28 @@ class HTMLProcessor:
# Client-side modifications # 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<span class="quote">\2</span>\3', html) html = self.quote_regex.sub(r'\1<span class="quote">\2</span>\3', html)
html = self.spoiler_regex.sub(
r'\1<font color="#00000000">\2</font>\3', html)
if not inline: if not inline:
return html return html
@ -247,6 +259,7 @@ class HTMLProcessor:
inline: bool = False, inline: bool = False,
outgoing: bool = False, outgoing: bool = False,
display_name_mentions: Optional[Dict[str, str]] = None, display_name_mentions: Optional[Dict[str, str]] = None,
extra_attributes: set = set(),
) -> dict: ) -> dict:
"""Return an html_sanitizer configuration.""" """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 [ username_link_regexes = [re.compile(r) for r in [
rf"(?<!\w)(?P<body>{re.escape(name or user_id)})(?!\w)(?P<host>)" rf"(?<!\w)(?P<body>{re.escape(name or user_id)})(?!\w)(?P<host>)"
for user_id, name in (display_name_mentions or {}).items() for user_id, name in (display_name_mentions or {}).items()

View File

@ -771,7 +771,7 @@ class MatrixClient(nio.AsyncClient):
) -> None: ) -> None:
if reply_to_event_id is None or reply_to_event_id == "": if reply_to_event_id is None or reply_to_event_id == "":
await self.send_fake_notice( 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: else:
reaction = emoji.emojize(text, language="alias") reaction = emoji.emojize(text, language="alias")
await self.send_reaction(room_id, reaction, reply_to_event_id) 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) to_html = from_md(text, outgoing=True)
echo_body = from_md(text) echo_body = from_md(text)
to_html = f"<span data-mx-spoiler>{to_html}</span>" if to_html.startswith("<p>") and to_html.endswith("</p>"):
echo_body = f"<span data-mx-spoiler>{echo_body}</span>" # we want to make sure the <span> is inside the <p>, otherwise the
# black bar will be too wide
inner_html = to_html[len('<p>'):-len('</p>')]
to_html = f"<p><span data-mx-spoiler>{inner_html}</span></p>"
else:
to_html = f"<span data-mx-spoiler>{to_html}</span>"
if echo_body.startswith("<p>") and echo_body.endswith("</p>"):
inner_html = echo_body[len('<p>'):-len('</p>')]
echo_body = f"<p><span data-mx-spoiler>{inner_html}</span></p>"
else:
echo_body = f"<span data-mx-spoiler>{echo_body}</span>"
await self._send_text( await self._send_text(
room_id, room_id,
@ -816,7 +826,7 @@ class MatrixClient(nio.AsyncClient):
if reply_to_event_id is None or reply_to_event_id == "": if reply_to_event_id is None or reply_to_event_id == "":
await self.send_fake_notice( await self.send_fake_notice(
room_id, room_id,
"Please reply to a message with /unspoiler to unspoiler it 🙃", "Please reply to a message with /unspoiler to unspoiler it",
) )
else: else:
spoiler_event: Event = \ spoiler_event: Event = \
@ -835,16 +845,31 @@ class MatrixClient(nio.AsyncClient):
async def send_reaction( async def send_reaction(
self, self,
room_id: str, room_id: str,
key: str, key: str,
reacts_to: str, reacts_to: str,
) -> None: ) -> None:
# local event id in model isn't necessarily the actual event id # local event id in model isn't necessarily the actual event id
reacts_to_event_id = self.models[ reacts_to_event = self.models[
self.user_id, room_id, "events"][reacts_to].event_id 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] = { content: Dict[str, Any] = {
"m.relates_to": { "m.relates_to": {
@ -858,8 +883,25 @@ class MatrixClient(nio.AsyncClient):
content[f"{__reverse_dns__}.transaction_id"] = str(tx_id) content[f"{__reverse_dns__}.transaction_id"] = str(tx_id)
await self.pause_while_offline() await self.pause_while_offline()
await self._send_message( try:
room_id, content, item_uuid, message_type = "m.reaction") 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 # only update the UI after the reaction is sent, to not be misleading
await self._register_reaction( await self._register_reaction(
@ -1222,7 +1264,7 @@ class MatrixClient(nio.AsyncClient):
event = Event( event = Event(
id = f"echo-{transaction_id}", id = f"echo-{transaction_id}",
event_id = "", event_id = f"echo-{transaction_id}" if fake_event else "",
event_type = event_type, event_type = event_type,
date = datetime.now(), date = datetime.now(),
sender_id = self.user_id, sender_id = self.user_id,