新增 iMessage 通道适配器完整实现,包含: 1. 核心适配器与工具工厂导出 2. 运行时存储、反射防护、会话路由等基础组件 3. 消息信封、线程管理、回复上下文格式化 4. Tapback 表情反应处理、自定义异常体系 5. 审批按钮、联系人解析、速率限制功能 6. 文本净化、目标解析、缓存管理模块 7. 配置 schema、多账户支持、安装向导等配置模块 8. 审计日志、媒体AI处理等扩展功能
85 lines
3.1 KiB
Python
85 lines
3.1 KiB
Python
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from yuxi.utils.logging_config import logger
|
||
|
||
|
||
async def describe_image(image_data: bytes, vision_api_url: str = "", vision_api_key: str = "") -> str:
|
||
"""通过外部 Vision API 描述图片内容。
|
||
|
||
如果未配置 API URL,返回基本元数据描述。
|
||
"""
|
||
if not vision_api_url:
|
||
return f"Image attachment ({len(image_data)} bytes)"
|
||
|
||
try:
|
||
import base64
|
||
|
||
import aiohttp
|
||
|
||
encoded = base64.b64encode(image_data).decode("utf-8")
|
||
payload = {
|
||
"model": "gpt-4o-mini",
|
||
"messages": [
|
||
{
|
||
"role": "user",
|
||
"content": [
|
||
{"type": "text", "text": "Describe this image briefly in one sentence."},
|
||
{
|
||
"type": "image_url",
|
||
"image_url": {"url": f"data:image/jpeg;base64,{encoded}"},
|
||
},
|
||
],
|
||
}
|
||
],
|
||
"max_tokens": 100,
|
||
}
|
||
headers = {"Authorization": f"Bearer {vision_api_key}"}
|
||
async with aiohttp.ClientSession() as session:
|
||
async with session.post(
|
||
vision_api_url, json=payload, headers=headers, timeout=aiohttp.ClientTimeout(total=15)
|
||
) as resp:
|
||
if resp.status == 200:
|
||
data = await resp.json()
|
||
return data["choices"][0]["message"]["content"]
|
||
return f"Image attachment ({len(image_data)} bytes)"
|
||
except Exception as e:
|
||
logger.warning(f"[iMessage/AI] Vision API failed: {e}")
|
||
return f"Image attachment ({len(image_data)} bytes)"
|
||
|
||
|
||
async def transcribe_audio(audio_data: bytes, transcription_api_url: str = "", transcription_api_key: str = "") -> str:
|
||
"""通过外部 API 转录音频内容。"""
|
||
if not transcription_api_url:
|
||
return f"Audio attachment ({len(audio_data)} bytes)"
|
||
|
||
try:
|
||
import aiohttp
|
||
from aiohttp import FormData
|
||
|
||
form = FormData()
|
||
form.add_field("file", audio_data, filename="audio.caf", content_type="audio/x-caf")
|
||
form.add_field("model", "whisper-1")
|
||
|
||
headers = {"Authorization": f"Bearer {transcription_api_key}"}
|
||
async with aiohttp.ClientSession() as session:
|
||
async with session.post(
|
||
transcription_api_url, data=form, headers=headers, timeout=aiohttp.ClientTimeout(total=30)
|
||
) as resp:
|
||
if resp.status == 200:
|
||
data = await resp.json()
|
||
return data.get("text", "")
|
||
return f"Audio attachment ({len(audio_data)} bytes)"
|
||
except Exception as e:
|
||
logger.warning(f"[iMessage/AI] Transcription API failed: {e}")
|
||
return f"Audio attachment ({len(audio_data)} bytes)"
|
||
|
||
|
||
def is_vision_configured(config: dict[str, Any]) -> bool:
|
||
return bool(config.get("vision_api_url") and config.get("vision_api_key"))
|
||
|
||
|
||
def is_transcription_configured(config: dict[str, Any]) -> bool:
|
||
return bool(config.get("transcription_api_url") and config.get("transcription_api_key"))
|