新增腾讯短信(Tencent SMS)渠道扩展,支持在 Yuxi 平台中集成腾讯云短信渠道。 包含以下功能模块: - client: 腾讯云短信 API 客户端封装 - plugin: 渠道插件核心 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - outbound: 外发消息管理 - streaming: 流式消息处理 - security: 安全校验 - dedupe: 消息去重 - delivery: 送达状态回调 - compliance: 合规管理 - frequency: 频率控制 - templates: 短信模板 - agent_prompt: Agent 提示词 - types: 类型定义
221 lines
9.3 KiB
Python
221 lines
9.3 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
from .types import SmsScene, SmsTemplateConfig, TemplateType, TencentSmsAccount
|
|
|
|
|
|
class TencentSmsConfigAdapter:
|
|
ENV_MAP = {
|
|
"secret_id": "TENCENT_SMS_SECRET_ID",
|
|
"secret_key": "TENCENT_SMS_SECRET_KEY",
|
|
"sms_sdk_app_id": "TENCENT_SMS_SDK_APP_ID",
|
|
"sign_name": "TENCENT_SMS_SIGN_NAME",
|
|
"region": "TENCENT_SMS_REGION",
|
|
}
|
|
|
|
def __init__(self):
|
|
self._config: dict | None = None
|
|
|
|
def set_config(self, config: dict) -> None:
|
|
self._config = config
|
|
|
|
def list_account_ids(self, config: dict) -> list[str]:
|
|
accounts = config.get("accounts", [])
|
|
if not accounts:
|
|
sid = os.environ.get("TENCENT_SMS_SECRET_ID", "")
|
|
if sid:
|
|
return ["default"]
|
|
return []
|
|
return [a.get("account_id", str(i)) for i, a in enumerate(accounts)]
|
|
|
|
async def resolve_account(self, account_id: str) -> dict:
|
|
if self._config is None:
|
|
return {}
|
|
accounts = self._config.get("accounts", [])
|
|
if not accounts:
|
|
sid = os.environ.get("TENCENT_SMS_SECRET_ID", "")
|
|
if sid and account_id in ("default", "0", ""):
|
|
return {
|
|
"account_id": "default",
|
|
"secret_id": sid,
|
|
"secret_key": os.environ.get("TENCENT_SMS_SECRET_KEY", ""),
|
|
"sms_sdk_app_id": os.environ.get("TENCENT_SMS_SDK_APP_ID", ""),
|
|
"sign_name": os.environ.get("TENCENT_SMS_SIGN_NAME", ""),
|
|
"region": os.environ.get("TENCENT_SMS_REGION", "ap-guangzhou"),
|
|
}
|
|
return {}
|
|
for a in accounts:
|
|
if a.get("account_id", str(accounts.index(a))) == account_id:
|
|
return a
|
|
return {}
|
|
|
|
def _parse_account(self, account: dict) -> TencentSmsAccount:
|
|
secret_id = account.get("secret_id") or os.environ.get("TENCENT_SMS_SECRET_ID", "")
|
|
secret_key = account.get("secret_key") or os.environ.get("TENCENT_SMS_SECRET_KEY", "")
|
|
sms_sdk_app_id = account.get("sms_sdk_app_id") or os.environ.get("TENCENT_SMS_SDK_APP_ID", "")
|
|
sign_name = account.get("sign_name") or os.environ.get("TENCENT_SMS_SIGN_NAME", "")
|
|
region = account.get("region") or os.environ.get("TENCENT_SMS_REGION", "ap-guangzhou")
|
|
|
|
templates = []
|
|
for tpl in account.get("templates", []):
|
|
templates.append(
|
|
SmsTemplateConfig(
|
|
scene=SmsScene(tpl.get("scene", "notification")),
|
|
template_id=tpl.get("template_id", ""),
|
|
sign_name=tpl.get("sign_name", sign_name),
|
|
template_type=TemplateType(tpl.get("template_type", "0")),
|
|
param_count=tpl.get("param_count", 1),
|
|
max_daily=tpl.get("max_daily", 1000),
|
|
description=tpl.get("description", ""),
|
|
template_format=tpl.get("template_format", ""),
|
|
)
|
|
)
|
|
|
|
return TencentSmsAccount(
|
|
account_id=account.get("account_id", "default"),
|
|
secret_id=secret_id,
|
|
secret_key=secret_key,
|
|
sms_sdk_app_id=sms_sdk_app_id,
|
|
sign_name=sign_name,
|
|
region=region,
|
|
endpoint=account.get("endpoint", "sms.tencentcloudapi.com"),
|
|
callback_url=account.get("callback_url", "/webhook/tencent-sms/callback"),
|
|
reply_callback_url=account.get("reply_callback_url", "/webhook/tencent-sms/reply-callback"),
|
|
templates=templates,
|
|
dm_policy=account.get("dm_policy", "open"),
|
|
allow_from=account.get("allow_from", []),
|
|
daily_limit=account.get("daily_limit", 1000),
|
|
interval_seconds=account.get("interval_seconds", 30),
|
|
phone_daily_limit=account.get("phone_daily_limit", 10),
|
|
poll_interval_seconds=account.get("poll_interval_seconds", 120),
|
|
enabled=account.get("enabled", True),
|
|
extend_code=account.get("extend_code", ""),
|
|
sender_id=account.get("sender_id", ""),
|
|
allowed_callback_ips=account.get("allowed_callback_ips", []),
|
|
)
|
|
|
|
def is_configured(self, account: dict) -> bool:
|
|
ta = self._parse_account(account)
|
|
return ta.is_configured
|
|
|
|
def is_enabled(self, account: dict) -> bool:
|
|
ta = self._parse_account(account)
|
|
return ta.enabled and ta.is_configured
|
|
|
|
def disabled_reason(self, account: dict) -> str:
|
|
ta = self._parse_account(account)
|
|
if not ta.secret_id:
|
|
return "\u7f3a\u5c11 SecretId"
|
|
if not ta.secret_key:
|
|
return "\u7f3a\u5c11 SecretKey"
|
|
if not ta.sms_sdk_app_id:
|
|
return "\u7f3a\u5c11 SmsSdkAppId"
|
|
if not ta.sign_name:
|
|
return "\u672a\u914d\u7f6e\u77ed\u4fe1\u7b7e\u540d (SignName)"
|
|
if not ta.templates:
|
|
return "\u672a\u914d\u7f6e\u77ed\u4fe1\u6a21\u677f"
|
|
if not ta.enabled:
|
|
return "\u6e20\u9053\u5df2\u7981\u7528"
|
|
return ""
|
|
|
|
def describe_account(self, account: dict) -> dict:
|
|
ta = self._parse_account(account)
|
|
return {
|
|
"account_id": ta.account_id,
|
|
"sms_sdk_app_id": ta.sms_sdk_app_id,
|
|
"sign_name": ta.sign_name,
|
|
"region": ta.region,
|
|
"dm_policy": ta.dm_policy,
|
|
"template_count": len(ta.templates),
|
|
"template_scenes": [t.scene.value for t in ta.templates],
|
|
}
|
|
|
|
def config_schema(self) -> dict:
|
|
return {
|
|
"type": "object",
|
|
"properties": {
|
|
"secret_id": {
|
|
"type": "string",
|
|
"description": "\u817e\u8baf\u4e91 API SecretId\uff08CAM \u63a7\u5236\u53f0\u83b7\u53d6\uff09",
|
|
},
|
|
"secret_key": {
|
|
"type": "string",
|
|
"description": "\u817e\u8baf\u4e91 API SecretKey",
|
|
"format": "password",
|
|
},
|
|
"sms_sdk_app_id": {
|
|
"type": "string",
|
|
"description": "\u77ed\u4fe1\u5e94\u7528 ID\uff08\u63a7\u5236\u53f0\u5e94\u7528\u7ba1\u7406 -> SDK AppID\uff09",
|
|
},
|
|
"sign_name": {
|
|
"type": "string",
|
|
"description": "\u77ed\u4fe1\u7b7e\u540d\uff08\u5df2\u5ba1\u6838\u901a\u8fc7\uff0c\u4e0d\u542b\u3010\u3011\uff09",
|
|
},
|
|
"region": {
|
|
"type": "string",
|
|
"default": "ap-guangzhou",
|
|
"description": "API \u5730\u57df",
|
|
},
|
|
"templates": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"scene": {
|
|
"type": "string",
|
|
"enum": ["verification", "notification", "alert", "marketing"],
|
|
},
|
|
"template_id": {"type": "string"},
|
|
"sign_name": {"type": "string"},
|
|
"param_count": {"type": "integer", "minimum": 1, "maximum": 12},
|
|
"description": {"type": "string"},
|
|
"template_format": {"type": "string"},
|
|
},
|
|
"required": ["scene", "template_id", "param_count"],
|
|
},
|
|
},
|
|
"dm_policy": {
|
|
"type": "string",
|
|
"enum": ["open", "pairing", "allowlist", "disabled"],
|
|
"default": "open",
|
|
},
|
|
"allow_from": {
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"description": "\u5141\u8bb8\u63a5\u6536\u77ed\u4fe1\u7684\u624b\u673a\u53f7\u767d\u540d\u5355\uff08E.164 \u683c\u5f0f\uff09",
|
|
},
|
|
"daily_limit": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"maximum": 100000,
|
|
"default": 1000,
|
|
},
|
|
"phone_daily_limit": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"maximum": 50,
|
|
"default": 10,
|
|
},
|
|
"extend_code": {
|
|
"type": "string",
|
|
"description": "\u77ed\u4fe1\u7801\u53f7\u6269\u5c55\u53f7\uff08\u56fd\u9645/\u6e2f\u6fb3\u53f0\u77ed\u4fe1\u7528\uff09",
|
|
},
|
|
"sender_id": {
|
|
"type": "string",
|
|
"description": "\u56fd\u9645/\u6e2f\u6fb3\u53f0 Sender ID",
|
|
},
|
|
"allowed_callback_ips": {
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"description": "\u5141\u8bb8\u7684 Webhook \u56de\u8c03 IP \u767d\u540d\u5355",
|
|
},
|
|
"enabled": {"type": "boolean", "default": True},
|
|
},
|
|
"required": ["secret_id", "secret_key", "sms_sdk_app_id", "sign_name"],
|
|
}
|
|
|
|
def default_account_id(self, config: dict) -> str:
|
|
ids = self.list_account_ids(config)
|
|
return ids[0] if ids else "default"
|