ForcePilot/backend/package/yuxi/channel/extensions/imessage/config.py
Kris 4d52f634fe feat(imessage): 新增iMessage渠道插件完整实现
该提交新增了基于BlueBubbles的iMessage渠道插件,支持单聊和群组消息,包含文本、图片、语音、文件和视频消息收发,支持消息编辑、撤回、回复、 reactions和输入状态提示,同时实现了账号配置、安全校验、配对授权、消息格式化与分片等完整功能。
2026-05-21 10:50:15 +08:00

154 lines
6.8 KiB
Python

from __future__ import annotations
import os
class IMessageConfigAdapter:
CHANNEL_KEY = "imessage"
def _get_channel_config(self, config: dict) -> dict:
return config.get("channels", {}).get(self.CHANNEL_KEY, {})
def list_account_ids(self, config: dict) -> list[str]:
channel_config = self._get_channel_config(config)
accounts = channel_config.get("accounts", {})
return list(accounts.keys()) if accounts else ["default"]
async def resolve_account(self, config: dict, account_id: str = "default") -> dict | None:
channel_config = self._get_channel_config(config)
accounts = channel_config.get("accounts", {})
account = accounts.get(account_id, {})
server_url = (
account.get("server_url")
or channel_config.get("server_url")
or os.getenv("IMESSAGE_SERVER_URL", "")
)
password = (
account.get("password")
or channel_config.get("password")
or os.getenv("IMESSAGE_SERVER_PASSWORD", "")
)
if not server_url or not password:
return None
return {
"account_id": account_id,
"server_url": server_url,
"password": password,
"name": account.get("name", account_id),
"dm_policy": account.get("dm_policy", channel_config.get("dm_policy", "pairing")),
"allow_from": account.get("allow_from", channel_config.get("allow_from", [])),
"group_policy": account.get("group_policy", channel_config.get("group_policy", "allowlist")),
"group_allow_from": account.get("group_allow_from", channel_config.get("group_allow_from", [])),
"media_max_mb": account.get("media_max_mb", channel_config.get("media_max_mb", 16)),
"text_chunk_limit": account.get("text_chunk_limit", channel_config.get("text_chunk_limit", 4000)),
"streaming": account.get("streaming", channel_config.get("streaming", {})),
"config": {**channel_config, **account},
}
def is_configured(self, account: dict | None) -> bool:
return bool(account and account.get("server_url") and account.get("password"))
def is_enabled(self, account: dict | None) -> bool:
if account is None:
return False
return account.get("config", {}).get("enabled", True) if account else False
def disabled_reason(self, account: dict | None) -> str:
if not account:
return "Not configured"
if not account.get("server_url"):
return "Missing server URL"
if not account.get("password"):
return "Missing password"
return ""
async def resolve_allow_from(self, config: dict, account_id: str) -> list[str] | None:
account = await self.resolve_account(config, account_id)
if not account:
return None
return account.get("allow_from", [])
def describe_account(self, account: dict) -> dict:
return {
"account_id": account.get("account_id", ""),
"name": account.get("name", ""),
"configured": self.is_configured(account),
"server_url": account.get("server_url", ""),
}
def config_schema(self) -> dict:
return {
"type": "object",
"properties": {
"enabled": {"type": "boolean", "default": True},
"accounts": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"server_url": {"type": "string", "description": "BlueBubbles server URL"},
"password": {"type": "string", "description": "BlueBubbles server password"},
"name": {"type": "string"},
"dm_policy": {
"type": "string",
"enum": ["pairing", "allowlist", "open", "disabled"],
"default": "pairing",
},
"allow_from": {
"type": "array",
"items": {"type": "string"},
"description": "E.164 numbers or emails allowed to DM",
},
"group_policy": {
"type": "string",
"enum": ["open", "allowlist", "disabled"],
"default": "allowlist",
},
"group_allow_from": {
"type": "array",
"items": {"type": "string"},
},
"media_max_mb": {"type": "integer", "default": 16},
"text_chunk_limit": {"type": "integer", "default": 4000},
"streaming": {
"type": "object",
"properties": {
"mode": {"type": "string", "enum": ["block"], "default": "block"},
"min_chars": {"type": "integer", "default": 2000},
"coalesce_min_chars": {"type": "integer", "default": 400},
},
},
},
},
},
"default_account": {"type": "string", "default": "default"},
"groups": {
"type": "object",
"description": "Per-group config overrides keyed by chat GUID",
"additionalProperties": {
"type": "object",
"properties": {
"require_mention": {"type": "boolean", "description": "Require @mention to trigger"},
"group_policy": {
"type": "string",
"enum": ["open", "allowlist", "disabled"],
},
"system_prompt": {"type": "string", "description": "Group-specific system prompt"},
},
},
},
"streaming": {
"type": "object",
"description": "Default streaming settings",
"properties": {
"mode": {"type": "string", "enum": ["block"], "default": "block"},
"min_chars": {"type": "integer", "default": 2000},
"coalesce_min_chars": {"type": "integer", "default": 400},
},
},
},
}