ForcePilot/backend/package/yuxi/channel/extensions/zalo/config.py
Kris 5946478772 feat(channel): 添加小红书、XMPP、元宝和 Zalo 渠道扩展
新增小红书、XMPP、元宝、Zalo 四个渠道扩展。

小红书渠道扩展主要模块:config, gateway, webhook, outbound, streaming, pairing, security, dedupe, media, status, window

XMPP 渠道扩展主要模块:plugin, config, gateway, outbound, streaming, pairing, security, dedupe, accounts, commands, muc, rate_limiter, stanza_utils, status, monitor

元宝渠道扩展主要模块:plugin, client, config_schema, gateway, outbound(chunk/queue/transport), inbound(dispatcher), streaming, pairing, security, accounts, actions, commands, codec(biz/conn), session, shared, utils

Zalo 渠道扩展主要模块:api, config, gateway, webhook, outbound, pairing, security, session, polling, monitor, status
2026-05-21 12:04:05 +08:00

170 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import logging
logger = logging.getLogger(__name__)
class ZaloConfigAdapter:
def list_account_ids(self, config: dict) -> list[str]:
accounts = config.get("accounts", {})
if not accounts:
return ["default"] if self._env_token_exists("default") else []
return list(accounts.keys())
async def resolve_account(self, account_id: str) -> dict:
raw = self._load_raw_config(account_id)
return self.build_account(account_id, raw)
def is_configured(self, account: dict) -> bool:
return bool(account.get("bot_token"))
def is_enabled(self, account: dict) -> bool:
return account.get("enabled", True)
def describe_account(self, account: dict) -> dict:
token_source = account.get("token_source", "none")
return {
"account_id": account.get("account_id", ""),
"name": account.get("name", ""),
"token_source": token_source,
"dm_policy": account.get("dm_policy", "pairing"),
"configured": self.is_configured(account),
}
def default_account_id(self, config: dict) -> str:
return config.get("default_account", "default")
def config_schema(self) -> dict:
return {
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"title": "Zalo 渠道配置",
"properties": {
"bot_token": {
"type": "string",
"title": "Bot Token",
"description": "Zalo Bot API Token也可通过环境变量 ZALO_BOT_TOKEN 注入)",
"x-ui-password": True,
},
"dm_policy": {
"type": "string",
"title": "DM 策略",
"enum": ["pairing", "allowlist", "open", "disabled"],
"default": "pairing",
},
"group_policy": {
"type": "string",
"title": "群组策略",
"enum": ["open", "allowlist", "disabled"],
"default": "allowlist",
},
"webhook_url": {
"type": "string",
"title": "Webhook URL",
"description": "HTTPS Webhook URL生产推荐",
},
"webhook_secret": {
"type": "string",
"title": "Webhook Secret",
"description": "Webhook 签名密钥8-256 字符)",
"x-ui-password": True,
},
"media_max_mb": {
"type": "integer",
"title": "媒体大小上限 (MB)",
"default": 5,
"minimum": 1,
"maximum": 10,
},
"proxy": {
"type": "string",
"title": "HTTP 代理",
"description": "HTTP 代理 URL如 http://proxy:8080",
},
},
"required": ["bot_token"],
}
def _load_raw_config(self, account_id: str) -> dict:
return {}
def build_account(self, account_id: str, raw: dict) -> dict:
token, token_source = self._resolve_token(account_id, raw)
env_secret = os.environ.get("ZALO_WEBHOOK_SECRET", "")
allow_from = raw.get("allow_from", [])
group_allow_from = raw.get("group_allow_from", allow_from)
return {
"account_id": account_id,
"name": raw.get("name", account_id),
"bot_token": token,
"token_source": token_source,
"dm_policy": raw.get("dm_policy", "pairing"),
"group_policy": raw.get("group_policy", "allowlist"),
"allow_from": allow_from,
"group_allow_from": group_allow_from,
"webhook_url": raw.get("webhook_url", ""),
"webhook_secret": env_secret or raw.get("webhook_secret", ""),
"webhook_path": raw.get("webhook_path", ""),
"media_max_mb": raw.get("media_max_mb", 5),
"proxy": raw.get("proxy", ""),
"response_prefix": raw.get("response_prefix", ""),
"enabled": raw.get("enabled", True),
}
def _resolve_token(self, account_id: str, raw: dict) -> tuple[str, str]:
bot_token = raw.get("bot_token", "")
if isinstance(bot_token, dict):
env_key = bot_token.get("env", "")
if env_key and os.environ.get(env_key):
return os.environ[env_key], "env"
file_path = bot_token.get("file", "")
if file_path:
try:
resolved = os.path.realpath(file_path)
except (OSError, ValueError):
pass
else:
try:
with open(resolved, encoding="utf-8") as f:
return f.read().strip(), "configFile"
except OSError:
logger.warning("Zalo tokenFile not readable: %s", file_path)
exec_val = bot_token.get("exec", "")
if exec_val:
return exec_val, "config"
return str(bot_token.get("value", "")), "config" if bot_token.get("value") else "none"
if bot_token and not bot_token.startswith("${"):
return bot_token, "config"
env_token = self._env_bot_token(account_id)
if env_token:
return env_token, "env"
token_file = raw.get("tokenFile", "")
if token_file:
try:
resolved = os.path.realpath(token_file)
except (OSError, ValueError):
return "", "none"
try:
with open(resolved, encoding="utf-8") as f:
return f.read().strip(), "configFile"
except OSError:
logger.warning("Zalo tokenFile not readable: %s", token_file)
return "", "none"
@staticmethod
def _env_bot_token(account_id: str) -> str:
key = f"ZALO_BOT_TOKEN_{account_id.upper()}"
return os.environ.get(key, "") or os.environ.get("ZALO_BOT_TOKEN", "")
@staticmethod
def _env_token_exists(account_id: str) -> bool:
return bool(ZaloConfigAdapter._env_bot_token(account_id))