|
@@ -431,6 +431,12 @@ def _normalize_reaction_emoji(emoji: str) -> str:
|
|
|
return "✅" if stripped in _CHECK_EMOJI_ALIASES else stripped
|
|
return "✅" if stripped in _CHECK_EMOJI_ALIASES else stripped
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _log_emoji_codepoints(emoji: str) -> str:
|
|
|
|
|
+ """An emoji rendered as codepoints, for log lines that must distinguish
|
|
|
|
|
+ look-alikes — ✔️ (U+2714 FE0F) and ✅ (U+2705) differ only here."""
|
|
|
|
|
+ return " ".join(f"U+{ord(char):04X}" for char in emoji)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
# Reaction glyphs for the interactive prompt cards. The display forms keep
|
|
# Reaction glyphs for the interactive prompt cards. The display forms keep
|
|
|
# their variation selectors — they are what the card text and the seed
|
|
# their variation selectors — they are what the card text and the seed
|
|
|
# reactions show, and that is what should render as colour emoji. Reaction
|
|
# reactions show, and that is what should render as colour emoji. Reaction
|
|
@@ -2021,13 +2027,22 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
return False
|
|
return False
|
|
|
room_id = str(room_id)
|
|
room_id = str(room_id)
|
|
|
message_id = str(message_id)
|
|
message_id = str(message_id)
|
|
|
- emoji = _normalize_reaction_emoji(str(emoji))
|
|
|
|
|
|
|
+ # Keep the raw form for the log: normalizing first would hide exactly
|
|
|
|
|
+ # the codepoint a misbehaving client sent.
|
|
|
|
|
+ raw_emoji = str(emoji)
|
|
|
|
|
+ emoji = _normalize_reaction_emoji(raw_emoji)
|
|
|
actor_id = str(actor_id)
|
|
actor_id = str(actor_id)
|
|
|
|
|
|
|
|
is_clarify = message_id in self._clarify_prompts
|
|
is_clarify = message_id in self._clarify_prompts
|
|
|
is_approval = message_id in self._approval_prompts
|
|
is_approval = message_id in self._approval_prompts
|
|
|
if not is_clarify and not is_approval:
|
|
if not is_clarify and not is_approval:
|
|
|
return False
|
|
return False
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ "Chatto: reaction on prompt card %s: %s (raw %s)",
|
|
|
|
|
+ message_id,
|
|
|
|
|
+ emoji,
|
|
|
|
|
+ _log_emoji_codepoints(raw_emoji),
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
if is_clarify:
|
|
if is_clarify:
|
|
|
prompt = self._clarify_prompts.get(message_id)
|
|
prompt = self._clarify_prompts.get(message_id)
|
|
@@ -2038,6 +2053,12 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
|
|
|
|
|
# Room must match.
|
|
# Room must match.
|
|
|
if str(getattr(prompt, "chat_id", "")) != room_id:
|
|
if str(getattr(prompt, "chat_id", "")) != room_id:
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ "Chatto: reaction on prompt %s ignored - room %s, card lives in %s",
|
|
|
|
|
+ message_id,
|
|
|
|
|
+ room_id,
|
|
|
|
|
+ getattr(prompt, "chat_id", ""),
|
|
|
|
|
+ )
|
|
|
return True
|
|
return True
|
|
|
|
|
|
|
|
# Expiry handling.
|
|
# Expiry handling.
|
|
@@ -2059,10 +2080,18 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
|
|
|
|
|
# Authorization (fail-closed).
|
|
# Authorization (fail-closed).
|
|
|
if not self._is_allowed_user_id(actor_id):
|
|
if not self._is_allowed_user_id(actor_id):
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ "Chatto: reaction on prompt %s by unauthorized actor %s ignored",
|
|
|
|
|
+ message_id,
|
|
|
|
|
+ actor_id,
|
|
|
|
|
+ )
|
|
|
return True
|
|
return True
|
|
|
|
|
|
|
|
# Double-click guard.
|
|
# Double-click guard.
|
|
|
if getattr(prompt, "resolved", False):
|
|
if getattr(prompt, "resolved", False):
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ "Chatto: prompt %s already resolved - reaction ignored", message_id
|
|
|
|
|
+ )
|
|
|
return True
|
|
return True
|
|
|
if is_clarify and getattr(prompt, "awaiting_text", False):
|
|
if is_clarify and getattr(prompt, "awaiting_text", False):
|
|
|
return True
|
|
return True
|
|
@@ -2076,6 +2105,11 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
from tools import clarify_gateway # type: ignore[import-not-found]
|
|
from tools import clarify_gateway # type: ignore[import-not-found]
|
|
|
|
|
|
|
|
choices = list(getattr(prompt, "choices", []) or [])
|
|
choices = list(getattr(prompt, "choices", []) or [])
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ "Chatto: clarify prompt %s accepts: %s",
|
|
|
|
|
+ message_id,
|
|
|
|
|
+ [*choices, "Other"],
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
if emoji == _OTHER_EMOJI_KEY:
|
|
if emoji == _OTHER_EMOJI_KEY:
|
|
|
prompt.awaiting_text = True # type: ignore[assignment]
|
|
prompt.awaiting_text = True # type: ignore[assignment]
|
|
@@ -2132,6 +2166,11 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
|
|
|
|
|
# Approval prompt.
|
|
# Approval prompt.
|
|
|
choices_map = getattr(prompt, "choices_map", {}) or {}
|
|
choices_map = getattr(prompt, "choices_map", {}) or {}
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ "Chatto: approval prompt %s accepts: %s",
|
|
|
|
|
+ message_id,
|
|
|
|
|
+ [*choices_map],
|
|
|
|
|
+ )
|
|
|
if emoji in choices_map:
|
|
if emoji in choices_map:
|
|
|
choice = choices_map[emoji]
|
|
choice = choices_map[emoji]
|
|
|
prompt.resolved = True
|
|
prompt.resolved = True
|
|
@@ -2820,9 +2859,16 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
# NOTE: "projection_event" (and "caught_up") belong to realtime protocol
|
|
# NOTE: "projection_event" (and "caught_up") belong to realtime protocol
|
|
|
# v2 on unreleased Chatto main — chattolib speaks v1 and can never
|
|
# v2 on unreleased Chatto main — chattolib speaks v1 and can never
|
|
|
# deliver them here. Revisit when vendored chattolib gains v2 typing.
|
|
# deliver them here. Revisit when vendored chattolib gains v2 typing.
|
|
|
|
|
+ # The notification_* / viewer-state kinds are the server's per-viewer
|
|
|
|
|
+ # bookkeeping: they fire on every read-state change in every followed
|
|
|
|
|
+ # room, need no adapter action, and warning on each one buried real
|
|
|
|
|
+ # signals in noise.
|
|
|
elif event.kind in (
|
|
elif event.kind in (
|
|
|
"mention_notification",
|
|
"mention_notification",
|
|
|
"notification_dismissed",
|
|
"notification_dismissed",
|
|
|
|
|
+ "notification_occurrences_changed",
|
|
|
|
|
+ "notification_unread_state_changed",
|
|
|
|
|
+ "thread_viewer_state_changed",
|
|
|
"room_marked_as_read",
|
|
"room_marked_as_read",
|
|
|
"user_typing",
|
|
"user_typing",
|
|
|
"notification_created",
|
|
"notification_created",
|