ForcePilot/backend/package/yuxi/channel/extensions/matrix/config.py
Kris 4e9c6dd8ab feat(channel): 添加 Matrix 渠道扩展
新增 Matrix 渠道扩展,支持在 Yuxi 平台中集成 Matrix 去中心化通讯协议。

包含以下功能模块:
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- outbound: 外发消息管理
- streaming: 流式消息处理
- pairing: 用户配对与绑定
- security: 安全校验
- crypto: 端到端加密
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- session: 会话管理
- room_resolver: 房间解析
- dm_tracker: 私聊追踪
- rate_limiter: 速率限制
- actions: 动作处理
- constants: 常量定义
- utils: 工具函数
- types: 类型定义
2026-05-21 11:18:13 +08:00

284 lines
10 KiB
Python

from __future__ import annotations
import logging
import os
from .types import MatrixAccount
logger = logging.getLogger(__name__)
ENV_MAP = {
"MATRIX_HOMESERVER": "homeserver",
"MATRIX_ACCESS_TOKEN": "access_token",
"MATRIX_USER_ID": "user_id",
"MATRIX_PASSWORD": "password",
"MATRIX_DEVICE_ID": "device_id",
"MATRIX_DEVICE_NAME": "device_name",
"MATRIX_RECOVERY_KEY": "recovery_key",
}
def _apply_env_overrides(account: MatrixAccount) -> MatrixAccount:
for env_key, attr_name in ENV_MAP.items():
env_val = os.environ.get(env_key)
if env_val is not None:
setattr(account, attr_name, env_val)
env_dm_policy = os.environ.get("MATRIX_DM_POLICY")
if env_dm_policy:
account.dm_policy = env_dm_policy
env_group_policy = os.environ.get("MATRIX_GROUP_POLICY")
if env_group_policy:
account.group_policy = env_group_policy
return account
def _dict_to_account(data: dict) -> MatrixAccount:
return MatrixAccount(
account_id=data.get("account_id", "default"),
enabled=data.get("enabled", True),
homeserver=data.get("homeserver", ""),
allow_private_network=data.get("allow_private_network", False),
access_token=data.get("access_token", ""),
user_id=data.get("user_id", ""),
password=data.get("password", ""),
device_id=data.get("device_id", ""),
device_name=data.get("device_name", "ForcePilot Gateway"),
encryption=data.get("encryption", False),
recovery_key=data.get("recovery_key", ""),
initial_sync_limit=data.get("initial_sync_limit", 50),
dm_policy=data.get("dm_policy", "pairing"),
dm_allow_from=data.get("dm_allow_from", []),
dm_session_scope=data.get("dm_session_scope", "per-user"),
group_policy=data.get("group_policy", "allowlist"),
group_allow_from=data.get("group_allow_from", []),
rooms=data.get("rooms", {}),
auto_join=data.get("auto_join", "off"),
auto_join_allowlist=data.get("auto_join_allowlist", []),
streaming=data.get("streaming", "partial"),
block_streaming=data.get("block_streaming", False),
text_chunk_limit=data.get("text_chunk_limit", 4000),
media_max_mb=data.get("media_max_mb", 20),
thread_replies=data.get("thread_replies", "inbound"),
reply_to_mode=data.get("reply_to_mode", "off"),
ack_reaction=data.get("ack_reaction", ""),
reaction_notifications=data.get("reaction_notifications", "own"),
)
def list_account_ids(config: dict) -> list[str]:
accounts = config.get("accounts", {})
if not accounts:
return ["default"]
return list(accounts.keys())
def resolve_account(account_id: str, config: dict | None = None) -> dict:
if config is None:
config = {}
accounts = config.get("accounts", {})
data = accounts.get(account_id, {})
if not data:
data = {"account_id": account_id}
elif "account_id" not in data:
data = {**data, "account_id": account_id}
return data
def is_configured(account: dict) -> bool:
acct = _dict_to_account(account)
acct = _apply_env_overrides(acct)
return bool(acct.homeserver and acct.access_token and acct.user_id)
def is_enabled(account: dict, config: dict | None = None) -> bool:
return account.get("enabled", True)
def describe_account(account: dict, config: dict | None = None) -> dict:
acct = _dict_to_account(account)
acct = _apply_env_overrides(acct)
return {
"account_id": acct.account_id,
"homeserver": acct.homeserver,
"user_id": acct.user_id,
"device_id": acct.device_id,
"device_name": acct.device_name,
"encryption": acct.encryption,
"dm_policy": acct.dm_policy,
"group_policy": acct.group_policy,
"enabled": acct.enabled,
}
def inspect_account(config: dict, account_id: str | None = None) -> dict:
aid = account_id or "default"
accounts = config.get("accounts", {})
data = accounts.get(aid, {})
acct = _dict_to_account(data)
acct = _apply_env_overrides(acct)
return {
"account_id": acct.account_id,
"homeserver": acct.homeserver,
"user_id": acct.user_id,
"configured": bool(acct.homeserver and acct.access_token and acct.user_id),
"encryption": acct.encryption,
}
def default_account_id(config: dict) -> str:
return "default"
def set_account_enabled(config: dict, account_id: str, enabled: bool) -> dict:
accounts = config.setdefault("accounts", {})
acct = accounts.setdefault(account_id, {})
acct["enabled"] = enabled
return config
def delete_account(config: dict, account_id: str) -> dict:
accounts = config.get("accounts", {})
accounts.pop(account_id, None)
return config
def disabled_reason(account: dict, config: dict | None = None) -> str:
if account.get("enabled", True):
return ""
return "Account is disabled"
def unconfigured_reason(account: dict, config: dict | None = None) -> str:
acct = _dict_to_account(account)
acct = _apply_env_overrides(acct)
reasons = []
if not acct.homeserver:
reasons.append("homeserver not set")
if not acct.access_token:
reasons.append("access_token not set")
if not acct.user_id:
reasons.append("user_id not set")
return "; ".join(reasons) if reasons else ""
def resolve_allow_from(config: dict, account_id: str | None = None) -> list[str] | None:
aid = account_id or "default"
accounts = config.get("accounts", {})
data = accounts.get(aid, {})
acct = _dict_to_account(data)
acct = _apply_env_overrides(acct)
return list(acct.dm_allow_from)
def format_allow_from(config: dict, account_id: str | None, allow_from: list[str]) -> list[str]:
return allow_from
def has_configured_state(config: dict) -> bool:
accounts = config.get("accounts", {})
for data in accounts.values():
if is_configured(data):
return True
return False
def has_persisted_auth_state(config: dict) -> bool:
return has_configured_state(config)
def resolve_default_to(config: dict, account_id: str | None = None) -> str | None:
return None
def config_schema() -> dict:
return {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"accounts": {
"type": "object",
"default": {"default": {}},
"additionalProperties": {
"type": "object",
"properties": {
"account_id": {"type": "string", "default": "default"},
"enabled": {"type": "boolean", "default": True},
"homeserver": {
"type": "string",
"description": "Matrix Homeserver URL",
"placeholder": "https://matrix.example.org",
},
"access_token": {"type": "string", "description": "Matrix Access Token"},
"user_id": {"type": "string", "description": "Matrix User ID (@bot:example.org)"},
"password": {"type": "string", "description": "Login password (fallback)"},
"device_id": {"type": "string"},
"device_name": {"type": "string", "default": "ForcePilot Gateway"},
"encryption": {"type": "boolean", "default": False},
"recovery_key": {"type": "string"},
"sync_token": {"type": "string", "description": "Last sync token for incremental sync"},
"dm_policy": {
"type": "string",
"enum": ["pairing", "allowlist", "open", "disabled"],
"default": "pairing",
},
"dm_allow_from": {
"type": "array",
"items": {"type": "string"},
"default": [],
},
"dm_session_scope": {
"type": "string",
"enum": ["per-user", "per-room"],
"default": "per-user",
},
"group_policy": {
"type": "string",
"enum": ["open", "allowlist", "disabled"],
"default": "allowlist",
},
"group_allow_from": {
"type": "array",
"items": {"type": "string"},
"default": [],
},
"auto_join": {
"type": "string",
"enum": ["off", "allowlist", "all"],
"default": "off",
},
"auto_join_allowlist": {
"type": "array",
"items": {"type": "string"},
"default": [],
},
"streaming": {
"type": "string",
"enum": ["partial", "quiet", "off"],
"default": "partial",
},
"block_streaming": {"type": "boolean", "default": False},
"text_chunk_limit": {"type": "integer", "default": 4000},
"media_max_mb": {"type": "integer", "default": 20},
"thread_replies": {
"type": "string",
"enum": ["off", "inbound", "always"],
"default": "inbound",
},
"reply_to_mode": {
"type": "string",
"enum": ["off", "inbound"],
"default": "off",
},
"ack_reaction": {"type": "string", "default": ""},
"reaction_notifications": {
"type": "string",
"enum": ["off", "own", "all"],
"default": "own",
},
},
},
}
},
}