该提交新增了完整的BlueBubbles渠道插件,支持通过BlueBubbles Server集成iMessage功能,包含以下核心能力: 1. 支持私聊和群聊会话管理,自动区分会话类型 2. 完整的消息收发支持,包括文本、图片、语音、文件、视频消息 3. 支持消息反应、已读回执、消息编辑与撤回 4. 内置去重、防抖处理机制 5. 支持Webhook和WebSocket两种事件接收方式 6. 完善的权限与安全校验机制 7. 历史消息同步与抓包功能 8. TTS语音合成与发送支持 9. 群组管理能力,包括重命名、修改头像、增减成员等
77 lines
3.7 KiB
Python
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"))
|