新增BlueBubbles适配器全套核心工具类与服务,包含会话管理、消息去重、Webhook验证、缓存系统、账号配置解析、聊天消息处理等完整功能模块,支持iMessage消息收发、群管理、反应特效、语音合成等能力,提供完善的健康检查与配置校验流程。
101 lines
3.1 KiB
Python
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 []
|