该提交新增了基于BlueBubbles的iMessage渠道插件,支持单聊和群组消息,包含文本、图片、语音、文件和视频消息收发,支持消息编辑、撤回、回复、 reactions和输入状态提示,同时实现了账号配置、安全校验、配对授权、消息格式化与分片等完整功能。
154 lines
6.8 KiB
Python
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},
|
|
},
|
|
},
|
|
},
|
|
}
|