diff --git a/gateway/platforms/base.py b/gateway/platforms/base.py index a1fef589a2..551c0e86ad 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 fce22a9701..7b20bc19a7 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 3fa1149864..f72c31e1c1 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 df7df7db76..81c7d55f14 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: