ForcePilot/backend/package/yuxi/channels/adapters/bluebubbles/targets.py
Kris 6aa1d52a8b feat(bluebubbles): 实现完整的BlueBubbles适配器基础组件
新增BlueBubbles适配器全套核心工具类与服务,包含会话管理、消息去重、Webhook验证、缓存系统、账号配置解析、聊天消息处理等完整功能模块,支持iMessage消息收发、群管理、反应特效、语音合成等能力,提供完善的健康检查与配置校验流程。
2026-05-12 00:42:57 +08:00

101 lines
3.1 KiB
Python

from __future__ import annotations
from typing import Any
from yuxi.channels.adapters.bluebubbles.client import BlueBubblesClient
from yuxi.utils.logging_config import logger
def normalize_bluebubbles_handle(handle: str) -> str:
return handle.strip().lstrip("+")
def parse_target(target: str) -> dict[str, str]:
result: dict[str, str] = {"type": "unknown", "value": target}
if target.startswith("chat_guid:"):
result["type"] = "chat_guid"
result["value"] = target[len("chat_guid:") :]
elif target.startswith("group:"):
result["type"] = "group_identifier"
result["value"] = target[len("group:") :]
elif target.startswith("chat_id:"):
result["type"] = "chat_id"
result["value"] = target[len("chat_id:") :]
elif target.startswith("handle:"):
result["type"] = "handle"
result["value"] = normalize_bluebubbles_handle(target[len("handle:") :])
elif target.startswith("+"):
result["type"] = "handle"
result["value"] = normalize_bluebubbles_handle(target)
elif ";" in target:
result["type"] = "group_identifier"
result["value"] = target
else:
result["type"] = "chat_guid"
result["value"] = target
return result
async def resolve_chat_guid(
client: BlueBubblesClient,
target: str,
) -> str | None:
parsed = parse_target(target)
if parsed["type"] == "chat_guid":
return parsed["value"]
if parsed["type"] == "handle":
chats = await _query_chats(client)
for chat in chats:
participants = chat.get("participants", [])
if len(participants) == 1:
addr = participants[0].get("address", "")
if normalize_bluebubbles_handle(addr) == normalize_bluebubbles_handle(parsed["value"]):
return chat.get("guid")
return None
if parsed["type"] == "group_identifier":
chats = await _query_chats(client)
for chat in chats:
display_name = chat.get("displayName", "")
if display_name == parsed["value"]:
return chat.get("guid")
return None
return parsed["value"]
async def resolve_chat_guid_with_auto_create(
client: BlueBubblesClient,
target: str,
initial_text: str = "",
) -> str | None:
guid = await resolve_chat_guid(client, target)
if guid:
return guid
parsed = parse_target(target)
if parsed["type"] not in ("handle",):
return None
from yuxi.channels.adapters.bluebubbles.send import auto_create_chat
try:
new = await auto_create_chat(client, parsed["value"], initial_text)
return new.get("chatGuid")
except Exception:
logger.warning("[BlueBubbles] Auto-create chat failed for target=%s", target)
return None
async def _query_chats(client: BlueBubblesClient) -> list[dict[str, Any]]:
try:
result = await client.get("/api/v1/chat/query")
return result.get("data", [])
except Exception:
logger.warning("[BlueBubbles] Failed to query chats for target resolution")
return []