from __future__ import annotations import asyncio import logging from .types import SmsScene logger = logging.getLogger(__name__) MAX_SMS_SEGMENTS = 10 UCS2_CHARS_PER_SEGMENT = 67 class TencentSmsBlockStreamer: def __init__(self, max_segments: int = MAX_SMS_SEGMENTS, chars_per_segment: int = UCS2_CHARS_PER_SEGMENT): self._max_segments = max_segments self._chars_per_segment = chars_per_segment def chunk_text(self, text: str) -> list[str]: if not text: return [] segments: list[str] = [] remaining = text while remaining and len(segments) < self._max_segments: if len(remaining) <= self._chars_per_segment: segments.append(remaining.strip()) break cut = self._chars_per_segment for delimiter in ["\u3002", "\n", "\uff01", "\uff1f", "\uff0c", " ", ",", "."]: idx = remaining[: self._chars_per_segment].rfind(delimiter) if idx > self._chars_per_segment // 2: cut = idx + len(delimiter) break segment = remaining[:cut].strip() if segment: segments.append(segment) remaining = remaining[cut:].strip() if remaining and len(segments) < self._max_segments: truncated = remaining[: self._chars_per_segment - 3] + "..." segments.append(truncated) return segments async def stream_send( self, phone: str, segments: list[str], send_func, interval_ms: int = 500, ): for i, segment in enumerate(segments): prefix = f"[{i + 1}/{len(segments)}] " if i > 0 else "" content = f"{prefix}{segment}" try: result = await send_func(phone, content) logger.debug( "stream segment %d/%d: %s", i + 1, len(segments), result.serial_no if result.success else f"FAIL:{result.code}", ) except Exception as e: logger.exception("stream segment %d/%d failed: %s", i + 1, len(segments), e) if i < len(segments) - 1: await asyncio.sleep(interval_ms / 1000.0) async def stream_send_by_template( self, phone: str, scene: SmsScene, segments: list[str], send_by_template_func, interval_ms: int = 500, ): for i, segment in enumerate(segments): prefix = f"[{i + 1}/{len(segments)}] " if i > 0 else "" content = f"{prefix}{segment}" try: result = await send_by_template_func(phone, scene, [content]) logger.debug( "stream segment %d/%d: %s", i + 1, len(segments), result.serial_no if result.success else f"FAIL:{result.code}", ) except Exception as e: logger.exception("stream segment %d/%d failed: %s", i + 1, len(segments), e) if i < len(segments) - 1: await asyncio.sleep(interval_ms / 1000.0)