62 lines
1.6 KiB
Python
62 lines
1.6 KiB
Python
|
|
import asyncio
|
|||
|
|
import logging
|
|||
|
|
from collections.abc import Awaitable, Callable
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
MC_CHAT_MAX_CHARS = 256
|
|||
|
|
CHUNK_PREFIX_OVERHEAD = 8
|
|||
|
|
DELAY_BETWEEN_CHUNKS = 0.5
|
|||
|
|
|
|||
|
|
|
|||
|
|
def split_for_minecraft(
|
|||
|
|
content: str,
|
|||
|
|
max_chars: int = MC_CHAT_MAX_CHARS,
|
|||
|
|
page_indicator: bool = True,
|
|||
|
|
) -> list[str]:
|
|||
|
|
result: list[str] = []
|
|||
|
|
remaining = content
|
|||
|
|
|
|||
|
|
while remaining:
|
|||
|
|
available = max_chars
|
|||
|
|
|
|||
|
|
if len(remaining) <= available:
|
|||
|
|
result.append(remaining)
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
split_at = -1
|
|||
|
|
for sep in ("\n", "。", "!", "?", ". ", "! ", "? ", ",", ", ", " "):
|
|||
|
|
pos = remaining.rfind(sep, 0, available)
|
|||
|
|
if pos > available * 0.5:
|
|||
|
|
split_at = pos + len(sep)
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
if split_at == -1:
|
|||
|
|
split_at = available
|
|||
|
|
|
|||
|
|
result.append(remaining[:split_at].rstrip())
|
|||
|
|
remaining = remaining[split_at:].lstrip()
|
|||
|
|
|
|||
|
|
if page_indicator and len(result) > 1:
|
|||
|
|
total = len(result)
|
|||
|
|
result = [f"[{i + 1}/{total}] {chunk}" for i, chunk in enumerate(result)]
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def stream_block_minecraft(
|
|||
|
|
send_fn: Callable[[str], Awaitable[None]],
|
|||
|
|
content: str,
|
|||
|
|
chunk_delay: float = DELAY_BETWEEN_CHUNKS,
|
|||
|
|
) -> None:
|
|||
|
|
chunks = split_for_minecraft(content)
|
|||
|
|
total = len(chunks)
|
|||
|
|
|
|||
|
|
logger.info("Minecraft streaming: %d chars -> %d chunks", len(content), total)
|
|||
|
|
|
|||
|
|
for i, chunk in enumerate(chunks):
|
|||
|
|
await send_fn(chunk)
|
|||
|
|
if i < total - 1:
|
|||
|
|
logger.debug("Chunk %d/%d sent, waiting %.1fs", i + 1, total, chunk_delay)
|
|||
|
|
await asyncio.sleep(chunk_delay)
|