import type { OpenClawPluginApi } from 'openclaw/plugin-sdk/core'; import WebSocket from 'ws'; import type { VoceChatAccount } from './types/index.js'; // VoceChat 消息类型定义(保留现有结构,待讨论后更新) interface VoceChatMessage { mid: number; messageId: string; fromUid: number; fromName?: string; channelId: number; channelType: 'direct' | 'group'; content: string; createdAt: number; contentType?: 'text' | 'image' | 'file'; attachments?: Array<{ name: string; url: string; size: number; }>; } /** * 启动入站消息监听 * * TODO: 当前使用 WebSocket 实现,待讨论 Webhook 方案后更新 * Webhook 方案需要: * 1. 插件注册 HTTP 路由接收 Webhook 推送 * 2. 在 VoceChat 后台配置 Webhook URL * 3. 处理 Webhook 的校验(GET 请求返回 200) * 4. 解析 POST 推送的消息数据 */ export async function startInbound( api: OpenClawPluginApi, account: VoceChatAccount, onMessage: (message: any) => Promise, onError: (error: Error) => void ): Promise<{ stop: () => void }> { const { serverUrl, botApiToken } = account; const accountId = account.accountId; if (!serverUrl || !botApiToken) { throw new Error('VoceChat: serverUrl and botApiToken are required'); } // 注意:当前使用 WebSocket 连接,后续应改为 Webhook const wsUrl = serverUrl.replace(/^http/, 'ws') + '/ws'; api.logger.info(`VoceChat [${accountId}]: Connecting to ${wsUrl}`); api.logger.warn(`VoceChat [${accountId}]: 当前使用 WebSocket,建议迁移到 Webhook 方案`); const ws = new WebSocket(wsUrl, { headers: { 'Authorization': `Bearer ${botApiToken}`, }, }); ws.on('open', () => { api.logger.info(`VoceChat [${accountId}]: WebSocket connected`); }); ws.on('message', async (data: WebSocket.Data) => { try { const event = JSON.parse(data.toString()); // 只处理消息事件 if (event.type !== 'message' || !event.data) { return; } const msg: VoceChatMessage = event.data; // 转换为 OpenClaw 标准消息格式 const message = { id: msg.messageId || String(msg.mid), text: msg.content, sender: { id: String(msg.fromUid), name: msg.fromName || `User-${msg.fromUid}`, }, chat: { id: String(msg.channelId), type: msg.channelType, }, timestamp: msg.createdAt, attachments: msg.attachments, }; await onMessage(message); } catch (err) { api.logger.error('VoceChat: Failed to process message\n' + err); } }); ws.on('error', (err) => { api.logger.error(`VoceChat [${accountId}]: WebSocket error\n` + err); onError(err); }); ws.on('close', () => { api.logger.info(`VoceChat [${accountId}]: WebSocket closed`); }); return { stop: () => { ws.close(); }, }; }