refactor(gateway): shared reaction-ack policy in base adapter

- base.on_processing_complete implements the opt-in remove-ack/add-outcome
  flow driven by _OK_EMOJI/_FAIL_EMOJI class attrs and the
  _add_reaction(chat_id, message_id, emoji)/_remove_reaction(chat_id,
  message_id) primitive shape; default stays a no-op.
- photon drops its override (exact behavioral match).
- slack/discord/feishu/matrix/telegram/google_chat keep overrides: divergent
  primitive signatures (team_id routing, raw message objects, reaction-id
  handles, replace-semantics setMessageReaction) or extra state protocols.
This commit is contained in:
teknium1 2026-07-29 09:39:01 -07:00 committed by Teknium
parent 58400a6793
commit 4fe5410d57
2 changed files with 49 additions and 22 deletions

View file

@ -4793,11 +4793,52 @@ class BasePlatformAdapter(ABC):
# Subclasses override these to react to message processing events
# (e.g. Discord adds 👀/✅/❌ reactions).
# Opt-in emoji set for the shared reaction-ack flow in
# ``on_processing_complete``. Adapters whose reaction primitives follow
# the ``_add_reaction(chat_id, message_id, emoji)`` /
# ``_remove_reaction(chat_id, message_id)`` shape can set these class
# attributes instead of overriding the hook. Left as ``None`` the hook
# stays a no-op (historical default).
_ACK_EMOJI: Optional[str] = None
_OK_EMOJI: Optional[str] = None
_FAIL_EMOJI: Optional[str] = None
async def on_processing_start(self, event: MessageEvent) -> None:
"""Hook called when background processing begins."""
async def on_processing_complete(self, event: MessageEvent, outcome: ProcessingOutcome) -> None:
"""Hook called when background processing completes."""
"""Hook called when background processing completes.
Default: shared reaction-ack flow swap the in-progress reaction for
a final success/failure reaction. Runs only when the adapter opts in
by setting ``_OK_EMOJI`` / ``_FAIL_EMOJI`` class attributes AND
defines ``_add_reaction`` / ``_remove_reaction`` primitives taking
``(chat_id, message_id[, emoji])``. Otherwise this is a no-op, as it
always was. Remove-then-add rather than a bare replace: deterministic
whether the platform replaces a sender's previous reaction or stacks
them. CANCELLED outcomes leave the message unreacted.
"""
if self._OK_EMOJI is None and self._FAIL_EMOJI is None:
return
add: Any = getattr(self, "_add_reaction", None)
remove: Any = getattr(self, "_remove_reaction", None)
if not callable(add) or not callable(remove):
return
enabled = getattr(self, "_reactions_enabled", None)
if callable(enabled) and not enabled():
return
chat_id = getattr(event.source, "chat_id", None)
message_id = getattr(event, "message_id", None)
if not chat_id or not message_id:
return
await remove(chat_id, message_id)
if outcome == ProcessingOutcome.SUCCESS:
if self._OK_EMOJI:
await add(chat_id, message_id, self._OK_EMOJI)
elif outcome == ProcessingOutcome.FAILURE:
if self._FAIL_EMOJI:
await add(chat_id, message_id, self._FAIL_EMOJI)
# CANCELLED: leave the message unreacted.
async def _run_processing_hook(self, hook_name: str, *args: Any, **kwargs: Any) -> None:
"""Run a lifecycle hook without letting failures break message flow."""

View file

@ -2295,27 +2295,13 @@ class PhotonAdapter(BasePlatformAdapter):
if chat_id and message_id:
await self._add_reaction(chat_id, message_id, "\U0001f440")
async def on_processing_complete(
self, event: MessageEvent, outcome: ProcessingOutcome
) -> None:
"""Swap the 👀 progress tapback for a 👍/👎 result.
Remove-then-add rather than a bare replace: deterministic whether the
platform replaces a sender's previous tapback or stacks them, and it
keeps the sidecar's reaction-handle slot coherent.
"""
if not self._reactions_enabled():
return
chat_id = getattr(event.source, "chat_id", None)
message_id = getattr(event, "message_id", None)
if not chat_id or not message_id:
return
await self._remove_reaction(chat_id, message_id)
if outcome == ProcessingOutcome.SUCCESS:
await self._add_reaction(chat_id, message_id, "\U0001f44d")
elif outcome == ProcessingOutcome.FAILURE:
await self._add_reaction(chat_id, message_id, "\U0001f44e")
# CANCELLED: leave the message unreacted.
# Shared reaction-ack flow (base.on_processing_complete): swap the 👀
# progress tapback for a 👍/👎 result. Remove-then-add rather than a bare
# replace: deterministic whether the platform replaces a sender's previous
# tapback or stacks them, and it keeps the sidecar's reaction-handle slot
# coherent. CANCELLED: leave the message unreacted.
_OK_EMOJI = "\U0001f44d"
_FAIL_EMOJI = "\U0001f44e"
async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
"""Return whatever we know about a Spectrum space id.