ForcePilot/backend/package/yuxi/channel/extensions/bluebubbles/config.py
Kris b018e3eda4 feat(bluebubbles): 新增BlueBubbles(iMessage)渠道插件完整实现
该提交新增了完整的BlueBubbles渠道插件,支持通过BlueBubbles Server集成iMessage功能,包含以下核心能力:
1.  支持私聊和群聊会话管理,自动区分会话类型
2.  完整的消息收发支持,包括文本、图片、语音、文件、视频消息
3.  支持消息反应、已读回执、消息编辑与撤回
4.  内置去重、防抖处理机制
5.  支持Webhook和WebSocket两种事件接收方式
6.  完善的权限与安全校验机制
7.  历史消息同步与抓包功能
8.  TTS语音合成与发送支持
9.  群组管理能力,包括重命名、修改头像、增减成员等
2026-05-21 10:40:33 +08:00

77 lines
3.7 KiB
Python

from dataclasses import dataclass
@dataclass
class CatchupConfig:
enabled: bool = True
max_age_minutes: int = 120
per_run_limit: int = 50
first_run_lookback_minutes: int = 30
max_failure_retries: int = 10
@dataclass
class GroupConfig:
require_mention: bool = False
tools: dict | None = None
system_prompt: str | None = None
class BlueBubblesConfigAdapter:
CHANNEL_KEY = "bluebubbles"
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", {})
if accounts:
return list(accounts.keys())
return ["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, {}) if account_id != "default" else {}
server_url = account.get("serverUrl") or channel_config.get("serverUrl")
password = account.get("password") or channel_config.get("password")
if not server_url or not password:
return None
return {
"account_id": account_id,
"server_url": server_url,
"password": password,
"dm_policy": account.get("dmPolicy") or channel_config.get("dmPolicy", "pairing"),
"allow_from": account.get("allowFrom") or channel_config.get("allowFrom", []),
"group_policy": account.get("groupPolicy") or channel_config.get("groupPolicy", "allowlist"),
"group_allow_from": account.get("groupAllowFrom") or channel_config.get("groupAllowFrom", []),
"groups": {**channel_config.get("groups", {}), **account.get("groups", {})},
"media_max_mb": account.get("mediaMaxMb") or channel_config.get("mediaMaxMb"),
"media_local_roots": account.get("mediaLocalRoots") or channel_config.get("mediaLocalRoots", []),
"text_chunk_limit": account.get("textChunkLimit") or channel_config.get("textChunkLimit", 4000),
"chunk_mode": account.get("chunkMode") or channel_config.get("chunkMode", "newline"),
"send_timeout_ms": account.get("sendTimeoutMs") or channel_config.get("sendTimeoutMs", 30000),
"send_read_receipts": account.get("sendReadReceipts", channel_config.get("sendReadReceipts", True)),
"coalesce_same_sender_dms": account.get(
"coalesceSameSenderDms", channel_config.get("coalesceSameSenderDms", False)
),
"enrich_group_participants_from_contacts": account.get(
"enrichGroupParticipantsFromContacts",
channel_config.get("enrichGroupParticipantsFromContacts", True),
),
"allow_private_network": account.get("network", {}).get("dangerouslyAllowPrivateNetwork")
or channel_config.get("network", {}).get("dangerouslyAllowPrivateNetwork", False),
"catchup": CatchupConfig(**{**channel_config.get("catchup", {}), **account.get("catchup", {})}),
"webhook_path": account.get("webhookPath") or channel_config.get("webhookPath", "/bluebubbles-webhook"),
"webhook_secret": account.get("webhookSecret") or channel_config.get("webhookSecret", ""),
"enabled": account.get("enabled", channel_config.get("enabled", True)),
"config": {**channel_config, **account},
}
def is_configured(self, account: dict | None) -> bool:
return bool(account and account.get("server_url") and account.get("password"))