60 lines
2.7 KiB
JavaScript
60 lines
2.7 KiB
JavaScript
/**
|
|||
|
|
* Wechaty -> 私域 Agent API 适配器(示例)
|
||
|
|
*
|
||
|
|
* 安装:npm i wechaty wechaty-puppet-wechat4u
|
||
|
|
* 启动:YAWAN_API_URL=https://agent.example.com \
|
||
|
|
* YAWAN_WECHAT_BOT_SECRET=... YAWAN_WECHAT_AGENT_ID=3 node scripts/wechat_bot_adapter.mjs
|
||
|
|
*
|
||
|
|
* 个人微信协议有封号风险。生产环境建议把本适配层替换为腾讯官方
|
||
|
|
* openclaw-weixin/iLink Bot,只保留下面 callAgent() 的 HTTP 调用。
|
||
|
|
*/
|
||
|
|
import crypto from 'node:crypto'
|
||
|
|
import { WechatyBuilder, Message } from 'wechaty'
|
||
|
|
|
||
|
|
const apiUrl = (process.env.YAWAN_API_URL || 'http://127.0.0.1:8010').replace(/\/$/, '')
|
||
|
|
const secret = process.env.YAWAN_WECHAT_BOT_SECRET || ''
|
||
|
|
const agentId = Number(process.env.YAWAN_WECHAT_AGENT_ID || 0)
|
||
|
|
if (!secret || !agentId) throw new Error('请设置 YAWAN_WECHAT_BOT_SECRET 和 YAWAN_WECHAT_AGENT_ID')
|
||
|
|
|
||
|
|
async function callAgent (conversationId, messageId, text) {
|
||
|
|
const timestamp = String(Math.floor(Date.now() / 1000))
|
||
|
|
const payload = `${timestamp}.${conversationId}.${messageId}.${text}`
|
||
|
|
const signature = crypto.createHmac('sha256', secret).update(payload).digest('hex')
|
||
|
|
const response = await fetch(`${apiUrl}/api/wechat/bot/reply`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'content-type': 'application/json',
|
||
|
|
'x-wechat-bot-timestamp': timestamp, 'x-wechat-bot-signature': signature },
|
||
|
|
body: JSON.stringify({ agent_id: agentId, conversation_id: conversationId,
|
||
|
|
message_id: messageId, text })
|
||
|
|
})
|
||
|
|
const data = await response.json().catch(() => ({}))
|
||
|
|
if (!response.ok) throw new Error(data.detail || `API ${response.status}`)
|
||
|
|
return data.reply || ''
|
||
|
|
}
|
||
|
|
|
||
|
|
const bot = WechatyBuilder.build({
|
||
|
|
name: process.env.YAWAN_WECHAT_SESSION || 'yawan-agent',
|
||
|
|
puppet: 'wechaty-puppet-wechat4u',
|
||
|
|
puppetOptions: { uos: true }
|
||
|
|
})
|
||
|
|
bot.on('scan', (qrcode, status) => console.log('请扫码登录微信:', status, qrcode))
|
||
|
|
bot.on('login', user => console.log('微信 Bot 已登录:', user.name()))
|
||
|
|
bot.on('logout', user => console.log('微信 Bot 已退出:', user.name()))
|
||
|
|
bot.on('message', async message => {
|
||
|
|
if (message.self() || message.type() !== Message.Type.Text) return
|
||
|
|
const text = message.text().trim()
|
||
|
|
if (!text) return
|
||
|
|
const room = await Promise.resolve(message.room())
|
||
|
|
const from = await Promise.resolve(message.from())
|
||
|
|
const conversationId = room?.id || from?.id || 'unknown'
|
||
|
|
const messageId = typeof message.id === 'function' ? message.id() : (message.id || crypto.randomUUID())
|
||
|
|
try {
|
||
|
|
const reply = await callAgent(conversationId, messageId, text)
|
||
|
|
if (reply) await message.say(reply)
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Agent 对话失败:', error.message)
|
||
|
|
await message.say('系统暂时繁忙,请稍后再试。')
|
||
|
|
}
|
||
|
|
})
|
||
|
|
await bot.start()
|