新增腾讯 IM(Tencent IM)渠道扩展,支持在 Yuxi 平台中集成腾讯即时通讯 IM 渠道。 包含以下功能模块: - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - outbound: 外发消息管理 - pairing: 用户配对与绑定 - security: 安全校验 - signature: 请求签名验证 - usersig: UserSig 生成 - dedupe: 消息去重 - status: 会话状态管理 - group: 群组管理 - types: 类型定义
207 lines
8.4 KiB
Python
207 lines
8.4 KiB
Python
import os
|
||
|
||
from yuxi.channel.extensions.tencent_im.types import TencentIMAccount
|
||
|
||
ENV_MAP = {
|
||
"sdk_appid": "TENCENT_IM_SDK_APPID",
|
||
"secret_key": "TENCENT_IM_SECRET_KEY",
|
||
"public_key": "TENCENT_IM_PUBLIC_KEY",
|
||
"admin_userid": "TENCENT_IM_ADMIN_USERID",
|
||
"region": "TENCENT_IM_REGION",
|
||
"dm_policy": "TENCENT_IM_DM_POLICY",
|
||
"usersig_expire_days": "TENCENT_IM_USERSIG_EXPIRE_DAYS",
|
||
"callback_verify_enabled": "TENCENT_IM_CALLBACK_VERIFY",
|
||
"callback_token": "TENCENT_IM_CALLBACK_TOKEN",
|
||
"callback_signature_version": "TENCENT_IM_CALLBACK_SIG_VERSION",
|
||
}
|
||
|
||
FIELD_MAP = {
|
||
"sdk_appid": "sdkAppid",
|
||
"secret_key": "secretKey",
|
||
"public_key": "publicKey",
|
||
"admin_userid": "adminUserid",
|
||
"region": "region",
|
||
"dm_policy": "dmPolicy",
|
||
"usersig_expire_days": "usersigExpireDays",
|
||
"callback_verify_enabled": "callbackVerifyEnabled",
|
||
"callback_token": "callbackToken",
|
||
"callback_signature_version": "callbackSignatureVersion",
|
||
}
|
||
|
||
|
||
class TencentIMConfig:
|
||
def list_account_ids(self, config: dict | None = None) -> list[str]:
|
||
if config and "accounts" in config:
|
||
return list(config["accounts"].keys())
|
||
|
||
if config and config.get("sdkAppid"):
|
||
return ["default"]
|
||
|
||
if self._env("sdk_appid"):
|
||
return ["default"]
|
||
|
||
return []
|
||
|
||
def resolve_account(self, account_id: str = "default") -> TencentIMAccount:
|
||
return TencentIMAccount(
|
||
account_id=account_id,
|
||
sdk_appid=int(self._config_value(account_id, "sdkAppid", "sdk_appid", "0")),
|
||
secret_key=self._config_value(account_id, "secretKey", "secret_key", ""),
|
||
public_key=self._config_value(account_id, "publicKey", "public_key", ""),
|
||
admin_userid=self._config_value(account_id, "adminUserid", "admin_userid", "admin"),
|
||
region=self._config_value(account_id, "region", "region", "ap-guangzhou"),
|
||
dm_policy=self._config_value(account_id, "dmPolicy", "dm_policy", "open"),
|
||
callback_verify_enabled=self._config_value_bool(
|
||
account_id, "callbackVerifyEnabled", "callback_verify_enabled", True
|
||
),
|
||
callback_token=self._config_value(account_id, "callbackToken", "callback_token", ""),
|
||
callback_signature_version=self._config_value(
|
||
account_id, "callbackSignatureVersion", "callback_signature_version", "v1"
|
||
),
|
||
usersig_expire_days=int(
|
||
self._config_value(account_id, "usersigExpireDays", "usersig_expire_days", "180")
|
||
),
|
||
streaming=True,
|
||
)
|
||
|
||
def is_configured(self, account: dict | None = None) -> bool:
|
||
if isinstance(account, dict):
|
||
sdk_appid = account.get("sdkAppid") or account.get("sdk_appid") or account.get("sdk_appid", 0)
|
||
secret_key = account.get("secretKey") or account.get("secret_key") or account.get("secret_key", "")
|
||
return bool(sdk_appid and secret_key)
|
||
return bool(self._env("sdk_appid") and self._env("secret_key"))
|
||
|
||
def is_enabled(self, account: dict, config: dict | None = None) -> bool:
|
||
if isinstance(account, dict):
|
||
return account.get("enabled", True) and self.is_configured(account)
|
||
return self.is_configured()
|
||
|
||
def disabled_reason(self, account: dict, config: dict | None = None) -> str:
|
||
if not self.is_configured(account):
|
||
return "SDKAppID 或 SecretKey 未配置"
|
||
if not account.get("enabled", True):
|
||
return "渠道已禁用"
|
||
return ""
|
||
|
||
def unconfigured_reason(self, account: dict, config: dict | None = None) -> str:
|
||
sdk_appid = account.get("sdkAppid", 0) if isinstance(account, dict) else 0
|
||
secret_key = account.get("secretKey", "") if isinstance(account, dict) else ""
|
||
if not sdk_appid:
|
||
return "缺少 SDKAppID"
|
||
if not secret_key:
|
||
return "缺少 SecretKey"
|
||
return ""
|
||
|
||
def describe_account(self, account: dict, config: dict | None = None) -> dict:
|
||
info = dict(account) if isinstance(account, dict) else {}
|
||
info.pop("secretKey", None)
|
||
info.pop("secret_key", None)
|
||
info["account_id"] = info.get("account_id", "default")
|
||
return info
|
||
|
||
def default_account_id(self, config: dict | None = None) -> str:
|
||
ids = self.list_account_ids(config)
|
||
return ids[0] if ids else "default"
|
||
|
||
def has_configured_state(self, config: dict | None = None) -> bool:
|
||
return self.list_account_ids(config) != []
|
||
|
||
def config_schema(self) -> dict:
|
||
return {
|
||
"$schema": "https://json-schema.org/draft-07/schema#",
|
||
"type": "object",
|
||
"title": "腾讯云 IM 渠道配置",
|
||
"properties": {
|
||
"sdkAppid": {
|
||
"type": "integer",
|
||
"title": "SDKAppID",
|
||
"description": "腾讯云 IM 控制台中应用的 SDKAppID",
|
||
},
|
||
"secretKey": {
|
||
"type": "string",
|
||
"title": "Secret Key",
|
||
"x-ui-password": True,
|
||
"description": "IM 控制台开发辅助工具中的密钥(key),用于生成 UserSig",
|
||
},
|
||
"publicKey": {
|
||
"type": "string",
|
||
"title": "Public Key",
|
||
"x-ui-password": True,
|
||
"description": "IM 控制台回调配置中的 public_key,用于回调签名校验",
|
||
},
|
||
"adminUserid": {
|
||
"type": "string",
|
||
"title": "管理员账号",
|
||
"default": "admin",
|
||
"description": "App 管理员账号标识,用于调用 REST API",
|
||
},
|
||
"region": {
|
||
"type": "string",
|
||
"title": "数据中心地域",
|
||
"enum": [
|
||
"ap-guangzhou",
|
||
"ap-beijing",
|
||
"ap-shanghai",
|
||
"ap-chengdu",
|
||
"ap-singapore",
|
||
"ap-seoul",
|
||
"eu-frankfurt",
|
||
"na-siliconvalley",
|
||
"ap-tokyo",
|
||
"ap-jakarta",
|
||
],
|
||
"default": "ap-guangzhou",
|
||
},
|
||
"dmPolicy": {
|
||
"type": "string",
|
||
"title": "DM 安全策略",
|
||
"enum": ["open", "pairing", "allowlist", "disabled"],
|
||
"default": "open",
|
||
},
|
||
"callbackVerifyEnabled": {
|
||
"type": "boolean",
|
||
"title": "启用回调签名校验",
|
||
"default": True,
|
||
},
|
||
"callbackToken": {
|
||
"type": "string",
|
||
"title": "回调 Token",
|
||
"x-ui-password": True,
|
||
"description": "腾讯云 IM 回调配置中的 Token,用于新签名方案 (v2)",
|
||
},
|
||
"callbackSignatureVersion": {
|
||
"type": "string",
|
||
"title": "回调签名版本",
|
||
"enum": ["v1", "v2"],
|
||
"default": "v1",
|
||
"description": "v1 使用 public_key 校验,v2 使用 Token + RequestTime 校验",
|
||
},
|
||
"usersigExpireDays": {
|
||
"type": "integer",
|
||
"title": "UserSig 有效期(天)",
|
||
"default": 180,
|
||
"minimum": 1,
|
||
"maximum": 180,
|
||
},
|
||
},
|
||
"required": ["sdkAppid", "secretKey", "adminUserid"],
|
||
}
|
||
|
||
def _config_value(self, account_id: str, db_key: str, env_key: str, default: str) -> str:
|
||
env_val = os.getenv(ENV_MAP.get(env_key, "")) if env_key else None
|
||
return env_val or default
|
||
|
||
def _config_value_bool(self, account_id: str, db_key: str, env_key: str, default: bool) -> bool:
|
||
env_key_name = ENV_MAP.get(env_key, "")
|
||
if env_key_name:
|
||
env_val = os.getenv(env_key_name)
|
||
if env_val is not None:
|
||
return env_val.lower() != "false"
|
||
return default
|
||
|
||
def _env(self, key: str) -> str | None:
|
||
env_key = ENV_MAP.get(key, "")
|
||
if env_key:
|
||
return os.getenv(env_key)
|
||
return None
|