from __future__ import annotations import asyncio import logging from .client import TencentSmsClient from .config import TencentSmsConfigAdapter from .delivery import SmsDeliveryTracker from .types import TencentSmsAccount from .webhook import TencentSmsWebhookHandler, set_webhook_handler logger = logging.getLogger("yuxi.channel.tencent-sms") class TencentSmsGatewayAdapter: def __init__(self): self._config = TencentSmsConfigAdapter() self._running = False self._tasks: list[asyncio.Task] = [] self._accounts: dict[str, TencentSmsAccount] = {} self._clients: dict[str, TencentSmsClient] = {} self._delivery_tracker = SmsDeliveryTracker() async def start(self, ctx) -> dict: config = ctx.config if hasattr(ctx, "config") else {} self._config.set_config(config) account = self._config._parse_account(config) if not account.is_configured: raise ValueError( f"腾讯云短信账户 {account.account_id} 未正确配置" ) self._accounts[account.account_id] = account client = TencentSmsClient(account) self._clients[account.account_id] = client probe_ok = await client.probe() if not probe_ok: logger.warning("腾讯云短信 Gateway probe 未通过") if hasattr(ctx, "outbound") and ctx.outbound: ctx.outbound.set_client(account.account_id, client, account) webhook_handler = TencentSmsWebhookHandler( allowed_ips=getattr(account, "allowed_callback_ips", None), delivery_tracker=self._delivery_tracker, ) set_webhook_handler(webhook_handler) self._running = True task = asyncio.create_task(self._periodic_pull(client, account)) self._tasks.append(task) logger.info( "腾讯云短信 Gateway 启动成功, account=%s, app_id=%s, sign=%s", account.account_id, account.sms_sdk_app_id, account.sign_name, ) return { "running": True, "account": account, "client": client, "accounts": self._accounts, "clients": self._clients, "delivery_tracker": self._delivery_tracker, } async def stop(self, ctx) -> None: self._running = False for task in self._tasks: task.cancel() self._tasks.clear() self._accounts.clear() self._clients.clear() logger.info("腾讯云短信 Gateway 已停止") def get_client(self, account_id: str = "default") -> TencentSmsClient | None: return self._clients.get(account_id) def get_account(self, account_id: str = "default") -> TencentSmsAccount | None: return self._accounts.get(account_id) async def probe(self, account: dict) -> bool: try: ta = self._config._parse_account(account) client = TencentSmsClient(ta) return await client.probe() except Exception as e: logger.warning("腾讯云短信 probe 失败: %s", e) return False async def _periodic_pull( self, client: TencentSmsClient, account: TencentSmsAccount, ): interval = max(account.poll_interval_seconds, 30) while self._running: try: await self._pull_all_statuses(client) await self._pull_all_replies(client) except asyncio.CancelledError: break except Exception as e: logger.exception("定时拉取任务异常: %s", e) await asyncio.sleep(interval) async def _pull_all_statuses(self, client: TencentSmsClient) -> int: total_processed = 0 batch_count = 0 max_batches = 10 while batch_count < max_batches: statuses = await client.pull_send_status(limit=100) if not statuses: break logger.info("Pull 拉取到 %d 条送达状态", len(statuses)) for item in statuses: self._delivery_tracker.handle_status_callback( { "SerialNo": item["serial_no"], "ReportStatus": item["report_status"], "PhoneNumber": item["phone_number"], "Description": item["description"], "ReportTime": item["user_receive_time"], "SessionContext": item["session_context"], } ) total_processed += 1 batch_count += 1 if len(statuses) < 100: break if total_processed > 0: logger.info("本轮共处理 %d 条送达状态", total_processed) return total_processed async def _pull_all_replies(self, client: TencentSmsClient) -> int: total_processed = 0 batch_count = 0 max_batches = 10 while batch_count < max_batches: replies = await client.pull_reply_status(limit=100) if not replies: break logger.info("Pull 拉取到 %d 条上行回复", len(replies)) for item in replies: logger.info( "上行回复: phone=%s, content=%s, time=%s", item["phone_number"], item["reply_content"], item["reply_time"], ) total_processed += 1 batch_count += 1 if len(replies) < 100: break if total_processed > 0: logger.info("本轮共处理 %d 条上行回复", total_processed) return total_processed