94 lines
3.2 KiB
Python
94 lines
3.2 KiB
Python
|
|
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)
|