From 125e5ef0899d4c60ec84ac720192ba05007ea7e1 Mon Sep 17 00:00:00 2001 From: Teknium Date: Tue, 7 Apr 2026 14:05:25 -0700 Subject: [PATCH] fix: extend caption substring fix to all platforms Move _merge_caption helper from TelegramAdapter to BasePlatformAdapter so all adapters inherit it. Fix the same substring-containment bug in: - gateway/platforms/base.py (photo burst merging) - gateway/run.py (priority photo follow-up merging) - gateway/platforms/feishu.py (media batch merging) The original fix only covered telegram.py. The same bug existed in base.py and run.py (pure substring check) and feishu.py (list membership without whitespace normalization). --- gateway/platforms/base.py | 21 +++++++++++++++++---- gateway/platforms/feishu.py | 5 +---- gateway/platforms/telegram.py | 16 ---------------- gateway/run.py | 5 +---- 4 files changed, 19 insertions(+), 28 deletions(-) diff --git a/gateway/platforms/base.py b/gateway/platforms/base.py index a1fef589a..551c0e86a 100644 --- a/gateway/platforms/base.py +++ b/gateway/platforms/base.py @@ -1105,6 +1105,22 @@ class BasePlatformAdapter(ABC): logger.error("[%s] Fallback send also failed: %s", self.name, fallback_result.error) return fallback_result + @staticmethod + def _merge_caption(existing_text: Optional[str], new_text: str) -> str: + """Merge a new caption into existing text, avoiding duplicates. + + Uses line-by-line exact match (not substring) to prevent false positives + where a shorter caption is silently dropped because it appears as a + substring of a longer one (e.g. "Meeting" inside "Meeting agenda"). + Whitespace is normalised for comparison. + """ + if not existing_text: + return new_text + existing_captions = [c.strip() for c in existing_text.split("\n\n")] + if new_text.strip() not in existing_captions: + return f"{existing_text}\n\n{new_text}".strip() + return existing_text + async def handle_message(self, event: MessageEvent) -> None: """ Process an incoming message. @@ -1164,10 +1180,7 @@ class BasePlatformAdapter(ABC): existing.media_urls.extend(event.media_urls) existing.media_types.extend(event.media_types) if event.text: - if not existing.text: - existing.text = event.text - elif event.text not in existing.text: - existing.text = f"{existing.text}\n\n{event.text}".strip() + existing.text = self._merge_caption(existing.text, event.text) else: self._pending_messages[session_key] = event return # Don't interrupt now - will run after current task completes diff --git a/gateway/platforms/feishu.py b/gateway/platforms/feishu.py index fce22a970..7b20bc19a 100644 --- a/gateway/platforms/feishu.py +++ b/gateway/platforms/feishu.py @@ -2065,10 +2065,7 @@ class FeishuAdapter(BasePlatformAdapter): existing.media_urls.extend(event.media_urls) existing.media_types.extend(event.media_types) if event.text: - if not existing.text: - existing.text = event.text - elif event.text not in existing.text.split("\n\n"): - existing.text = f"{existing.text}\n\n{event.text}" + existing.text = self._merge_caption(existing.text, event.text) existing.timestamp = event.timestamp if event.message_id: existing.message_id = event.message_id diff --git a/gateway/platforms/telegram.py b/gateway/platforms/telegram.py index 3fa114986..f72c31e1c 100644 --- a/gateway/platforms/telegram.py +++ b/gateway/platforms/telegram.py @@ -2213,22 +2213,6 @@ class TelegramAdapter(BasePlatformAdapter): if self._pending_photo_batch_tasks.get(batch_key) is current_task: self._pending_photo_batch_tasks.pop(batch_key, None) - @staticmethod - def _merge_caption(existing_text: Optional[str], new_text: str) -> str: - """Merge a new caption into existing text, avoiding duplicates. - - Uses line-by-line exact match (not substring) to prevent false positives - where a shorter caption is silently dropped because it appears as a - substring of a longer one (e.g. "Meeting" inside "Meeting agenda"). - Whitespace is normalised for comparison. - """ - if not existing_text: - return new_text - existing_captions = [c.strip() for c in existing_text.split("\n\n")] - if new_text.strip() not in existing_captions: - return f"{existing_text}\n\n{new_text}".strip() - return existing_text - def _enqueue_photo_event(self, batch_key: str, event: MessageEvent) -> None: """Merge photo events into a pending batch and schedule flush.""" existing = self._pending_photo_batches.get(batch_key) diff --git a/gateway/run.py b/gateway/run.py index df7df7db7..81c7d55f1 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -1987,10 +1987,7 @@ class GatewayRunner: existing.media_urls.extend(event.media_urls) existing.media_types.extend(event.media_types) if event.text: - if not existing.text: - existing.text = event.text - elif event.text not in existing.text: - existing.text = f"{existing.text}\n\n{event.text}".strip() + existing.text = BasePlatformAdapter._merge_caption(existing.text, event.text) else: adapter._pending_messages[_quick_key] = event else: