@zzedbot/yunzhijia 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @zzedbot/yunzhijia might be problematic. Click here for more details.
- package/package.json +3 -3
- package/dist/channel.d.ts +0 -45
- package/dist/channel.js +0 -228
- package/dist/config.d.ts +0 -3
- package/dist/config.js +0 -27
- package/dist/http.d.ts +0 -6
- package/dist/http.js +0 -214
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -5
- package/dist/plugin-fixed.d.ts +0 -3
- package/dist/plugin-fixed.js +0 -106
- package/dist/plugin-simple.d.ts +0 -2
- package/dist/plugin-simple.js +0 -104
- package/dist/plugin.d.ts +0 -2
- package/dist/plugin.js +0 -272
- package/dist/receiver.d.ts +0 -17
- package/dist/receiver.js +0 -110
- package/dist/runtime.d.ts +0 -3
- package/dist/runtime.js +0 -16
- package/dist/test.d.ts +0 -4
- package/dist/test.js +0 -76
- package/dist/types.d.ts +0 -53
- package/dist/types.js +0 -2
- package/dist/utils.d.ts +0 -34
- package/dist/utils.js +0 -108
- package/dist/webhook.d.ts +0 -9
- package/dist/webhook.js +0 -42
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zzedbot/yunzhijia",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "YunzhiJia channel plugin for OpenClaw",
|
|
5
|
-
"main": "
|
|
6
|
-
"types": "
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"types": "src/index.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"test": "echo \"Error: no test specified\" && exit 1"
|
package/dist/channel.d.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
interface YunzhijiaConfig {
|
|
2
|
-
appSecret: string;
|
|
3
|
-
webhookUrl: string;
|
|
4
|
-
receiveEndpoint: string;
|
|
5
|
-
}
|
|
6
|
-
export declare class YunzhijiaChannel {
|
|
7
|
-
private app;
|
|
8
|
-
private config;
|
|
9
|
-
private sessions;
|
|
10
|
-
constructor(config: YunzhijiaConfig);
|
|
11
|
-
private setupRoutes;
|
|
12
|
-
/**
|
|
13
|
-
* 验证云之家请求的签名
|
|
14
|
-
*/
|
|
15
|
-
private verifySignature;
|
|
16
|
-
/**
|
|
17
|
-
* 处理来自云之家的传入消息
|
|
18
|
-
*/
|
|
19
|
-
private handleIncomingMessage;
|
|
20
|
-
/**
|
|
21
|
-
* 更新会话状态
|
|
22
|
-
*/
|
|
23
|
-
private updateSession;
|
|
24
|
-
/**
|
|
25
|
-
* 通过OpenClaw网关处理消息
|
|
26
|
-
*/
|
|
27
|
-
private processWithOpenClaw;
|
|
28
|
-
/**
|
|
29
|
-
* 模拟OpenClaw处理(实际实现中应替换为真实的API调用)
|
|
30
|
-
*/
|
|
31
|
-
private simulateOpenClawProcessing;
|
|
32
|
-
/**
|
|
33
|
-
* 发送消息到云之家(通过Webhook)
|
|
34
|
-
*/
|
|
35
|
-
sendMessageToYunzhijia(content: string, sessionId?: string): Promise<boolean>;
|
|
36
|
-
/**
|
|
37
|
-
* 启动服务器
|
|
38
|
-
*/
|
|
39
|
-
start(port?: number): Promise<void>;
|
|
40
|
-
/**
|
|
41
|
-
* 开始会话清理任务
|
|
42
|
-
*/
|
|
43
|
-
private startSessionCleanup;
|
|
44
|
-
}
|
|
45
|
-
export {};
|
package/dist/channel.js
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import crypto from 'crypto';
|
|
3
|
-
import axios from 'axios';
|
|
4
|
-
export class YunzhijiaChannel {
|
|
5
|
-
app;
|
|
6
|
-
config;
|
|
7
|
-
sessions = new Map();
|
|
8
|
-
constructor(config) {
|
|
9
|
-
this.config = config;
|
|
10
|
-
this.app = express();
|
|
11
|
-
// 解析JSON请求体
|
|
12
|
-
this.app.use(express.json({
|
|
13
|
-
verify: (req, res, buf) => {
|
|
14
|
-
// 保存原始请求体以便签名验证
|
|
15
|
-
req.rawBody = buf;
|
|
16
|
-
}
|
|
17
|
-
}));
|
|
18
|
-
// 设置路由
|
|
19
|
-
this.setupRoutes();
|
|
20
|
-
}
|
|
21
|
-
setupRoutes() {
|
|
22
|
-
// 接收云之家消息的端点
|
|
23
|
-
this.app.post(this.config.receiveEndpoint, this.handleIncomingMessage.bind(this));
|
|
24
|
-
// 健康检查端点
|
|
25
|
-
this.app.get('/health', (req, res) => {
|
|
26
|
-
res.status(200).json({ status: 'OK', timestamp: Date.now() });
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* 验证云之家请求的签名
|
|
31
|
-
*/
|
|
32
|
-
verifySignature(req) {
|
|
33
|
-
const signature = req.headers['sign'];
|
|
34
|
-
const sessionId = req.headers['sessionid'];
|
|
35
|
-
if (!signature || !sessionId) {
|
|
36
|
-
console.error('Missing signature or sessionId');
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
// 获取原始请求体
|
|
40
|
-
const rawBody = req.rawBody.toString();
|
|
41
|
-
const message = JSON.parse(rawBody);
|
|
42
|
-
// 构建签名字符串
|
|
43
|
-
const summaryInfo = [
|
|
44
|
-
message.robotId,
|
|
45
|
-
message.robotName,
|
|
46
|
-
message.operatorOpenid,
|
|
47
|
-
message.operatorName,
|
|
48
|
-
message.time.toString(),
|
|
49
|
-
message.msgId,
|
|
50
|
-
message.content
|
|
51
|
-
].join(',');
|
|
52
|
-
// 使用HmacSHA1算法计算签名
|
|
53
|
-
const hmac = crypto.createHmac('sha1', this.config.appSecret);
|
|
54
|
-
hmac.update(summaryInfo);
|
|
55
|
-
const expectedSignature = hmac.digest('base64');
|
|
56
|
-
// 验证签名
|
|
57
|
-
const isValid = crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));
|
|
58
|
-
if (!isValid) {
|
|
59
|
-
console.error('Invalid signature:', {
|
|
60
|
-
received: signature,
|
|
61
|
-
expected: expectedSignature,
|
|
62
|
-
summaryInfo
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
return isValid;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* 处理来自云之家的传入消息
|
|
69
|
-
*/
|
|
70
|
-
async handleIncomingMessage(req, res) {
|
|
71
|
-
try {
|
|
72
|
-
// 验证签名
|
|
73
|
-
if (!this.verifySignature(req)) {
|
|
74
|
-
res.status(401).json({ error: 'Invalid signature' });
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const message = req.body;
|
|
78
|
-
const sessionId = req.headers['sessionid'];
|
|
79
|
-
console.log(`Received message from user ${message.operatorName} (${message.operatorOpenid}): ${message.content}`);
|
|
80
|
-
// 更新或创建会话
|
|
81
|
-
await this.updateSession(sessionId, message);
|
|
82
|
-
// 调用OpenClaw网关处理消息
|
|
83
|
-
const responseContent = await this.processWithOpenClaw(message, sessionId);
|
|
84
|
-
// 发送响应给云之家
|
|
85
|
-
const response = {
|
|
86
|
-
success: true,
|
|
87
|
-
data: {
|
|
88
|
-
type: 2,
|
|
89
|
-
content: responseContent
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
res.json(response);
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
console.error('Error handling incoming message:', error);
|
|
96
|
-
res.status(500).json({
|
|
97
|
-
success: false,
|
|
98
|
-
error: 'Internal server error'
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* 更新会话状态
|
|
104
|
-
*/
|
|
105
|
-
async updateSession(sessionId, message) {
|
|
106
|
-
let session = this.sessions.get(sessionId);
|
|
107
|
-
if (!session) {
|
|
108
|
-
session = {
|
|
109
|
-
id: sessionId,
|
|
110
|
-
userId: message.operatorOpenid,
|
|
111
|
-
lastActive: Date.now(),
|
|
112
|
-
context: []
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
// 更新最后活动时间
|
|
116
|
-
session.lastActive = Date.now();
|
|
117
|
-
// 保存消息到上下文(保留最近的10条消息)
|
|
118
|
-
session.context.push({
|
|
119
|
-
user: message.operatorName,
|
|
120
|
-
content: message.content,
|
|
121
|
-
timestamp: message.time
|
|
122
|
-
});
|
|
123
|
-
if (session.context.length > 10) {
|
|
124
|
-
session.context.shift(); // 移除最旧的消息
|
|
125
|
-
}
|
|
126
|
-
this.sessions.set(sessionId, session);
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* 通过OpenClaw网关处理消息
|
|
130
|
-
*/
|
|
131
|
-
async processWithOpenClaw(message, sessionId) {
|
|
132
|
-
try {
|
|
133
|
-
// 这里应该调用OpenClaw的API或WebSocket接口
|
|
134
|
-
// 由于我们不知道OpenClaw的内部API,这里模拟处理
|
|
135
|
-
console.log(`Processing message with OpenClaw: ${message.content}`);
|
|
136
|
-
// 模拟OpenClaw处理
|
|
137
|
-
// 在实际实现中,这里应该通过某种方式与OpenClaw网关通信
|
|
138
|
-
const processedContent = await this.simulateOpenClawProcessing(message.content);
|
|
139
|
-
return processedContent;
|
|
140
|
-
}
|
|
141
|
-
catch (error) {
|
|
142
|
-
console.error('Error processing with OpenClaw:', error);
|
|
143
|
-
return '抱歉,处理您的消息时出现错误。';
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* 模拟OpenClaw处理(实际实现中应替换为真实的API调用)
|
|
148
|
-
*/
|
|
149
|
-
async simulateOpenClawProcessing(content) {
|
|
150
|
-
// 这里应该是与OpenClaw网关的真实交互
|
|
151
|
-
// 为了演示,我们简单返回一个处理过的消息
|
|
152
|
-
// 模拟延迟
|
|
153
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
154
|
-
// 简单的消息处理逻辑
|
|
155
|
-
if (content.toLowerCase().includes('hello') || content.toLowerCase().includes('你好')) {
|
|
156
|
-
return '您好!我是云之家机器人,很高兴为您服务。';
|
|
157
|
-
}
|
|
158
|
-
else if (content.toLowerCase().includes('help') || content.toLowerCase().includes('帮助')) {
|
|
159
|
-
return '我可以帮您回答问题、提供信息或执行任务。请告诉我您需要什么帮助。';
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
return `我已收到您的消息:“${content}”。这是OpenClaw机器人的自动回复。`;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* 发送消息到云之家(通过Webhook)
|
|
167
|
-
*/
|
|
168
|
-
async sendMessageToYunzhijia(content, sessionId) {
|
|
169
|
-
try {
|
|
170
|
-
const response = {
|
|
171
|
-
success: true,
|
|
172
|
-
data: {
|
|
173
|
-
type: 2,
|
|
174
|
-
content: content
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
// 在实际实现中,这里应该调用云之家的Webhook API
|
|
178
|
-
// 根据提供的URL模板构建请求
|
|
179
|
-
const webhookUrl = `https://www.yunzhijia.com/gateway/robot/webhook/send?yzjtype=12&yzjtoken=eb2ce932214c48b2a604cc7d4d6034b2`;
|
|
180
|
-
console.log(`Sending message to Yunzhijia via webhook: ${content}`);
|
|
181
|
-
// 发送请求到云之家Webhook
|
|
182
|
-
const result = await axios.post(webhookUrl, response.data, {
|
|
183
|
-
headers: {
|
|
184
|
-
'Content-Type': 'application/json',
|
|
185
|
-
},
|
|
186
|
-
timeout: 3000 // 3秒超时
|
|
187
|
-
});
|
|
188
|
-
return result.status === 200;
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
console.error('Error sending message to Yunzhijia:', error);
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* 启动服务器
|
|
197
|
-
*/
|
|
198
|
-
async start(port = 3000) {
|
|
199
|
-
return new Promise((resolve) => {
|
|
200
|
-
const server = this.app.listen(port, () => {
|
|
201
|
-
console.log(`Yunzhijia Channel server running on port ${port}`);
|
|
202
|
-
console.log(`Receive endpoint: http://localhost:${port}${this.config.receiveEndpoint}`);
|
|
203
|
-
resolve();
|
|
204
|
-
});
|
|
205
|
-
// 处理会话清理的定时任务
|
|
206
|
-
this.startSessionCleanup();
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* 开始会话清理任务
|
|
211
|
-
*/
|
|
212
|
-
startSessionCleanup() {
|
|
213
|
-
// 每10分钟清理一次过期会话(超过30分钟无活动)
|
|
214
|
-
setInterval(() => {
|
|
215
|
-
const now = Date.now();
|
|
216
|
-
const expiredSessions = [];
|
|
217
|
-
for (const [sessionId, session] of this.sessions) {
|
|
218
|
-
if (now - session.lastActive > 30 * 60 * 1000) { // 30分钟
|
|
219
|
-
expiredSessions.push(sessionId);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
for (const sessionId of expiredSessions) {
|
|
223
|
-
this.sessions.delete(sessionId);
|
|
224
|
-
console.log(`Expired session removed: ${sessionId}`);
|
|
225
|
-
}
|
|
226
|
-
}, 10 * 60 * 1000); // 10分钟
|
|
227
|
-
}
|
|
228
|
-
}
|
package/dist/config.d.ts
DELETED
package/dist/config.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_ACCOUNT_ID = exports.yunzhijiaConfigSchema = exports.YunzhiJiaConfigSchema = void 0;
|
|
4
|
-
const zod_1 = require("zod");
|
|
5
|
-
const plugin_sdk_1 = require("openclaw/plugin-sdk");
|
|
6
|
-
// Define the configuration schema using Zod
|
|
7
|
-
const YunzhiJiaAccountConfigSchema = zod_1.z.object({
|
|
8
|
-
webhookToken: zod_1.z.string().optional(),
|
|
9
|
-
appSecret: zod_1.z.string().optional(),
|
|
10
|
-
skipSignatureVerification: zod_1.z.boolean().optional(),
|
|
11
|
-
name: zod_1.z.string().optional(),
|
|
12
|
-
enabled: zod_1.z.boolean().optional(),
|
|
13
|
-
requireMention: zod_1.z.boolean().optional(),
|
|
14
|
-
allowFrom: zod_1.z.array(zod_1.z.union([zod_1.z.string(), zod_1.z.number()])).optional(),
|
|
15
|
-
});
|
|
16
|
-
exports.YunzhiJiaConfigSchema = zod_1.z.object({
|
|
17
|
-
webhookToken: zod_1.z.string().optional(),
|
|
18
|
-
appSecret: zod_1.z.string().optional(),
|
|
19
|
-
enabled: zod_1.z.boolean().optional(),
|
|
20
|
-
name: zod_1.z.string().optional(),
|
|
21
|
-
requireMention: zod_1.z.boolean().optional(),
|
|
22
|
-
allowFrom: zod_1.z.array(zod_1.z.union([zod_1.z.string(), zod_1.z.number()])).optional(),
|
|
23
|
-
accounts: zod_1.z.record(YunzhiJiaAccountConfigSchema).optional(),
|
|
24
|
-
});
|
|
25
|
-
// Export the channel config schema in OpenClaw format
|
|
26
|
-
exports.yunzhijiaConfigSchema = (0, plugin_sdk_1.buildChannelConfigSchema)(exports.YunzhiJiaConfigSchema);
|
|
27
|
-
exports.DEFAULT_ACCOUNT_ID = "default";
|
package/dist/http.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
2
|
-
import type { YunzhiJiaConfig } from "./types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Handle incoming YunzhiJia webhook requests
|
|
5
|
-
*/
|
|
6
|
-
export declare function handleYunzhiJiaWebhook(req: IncomingMessage, res: ServerResponse, config: YunzhiJiaConfig): Promise<void>;
|
package/dist/http.js
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.handleYunzhiJiaWebhook = handleYunzhiJiaWebhook;
|
|
4
|
-
const node_buffer_1 = require("node:buffer");
|
|
5
|
-
const plugin_sdk_1 = require("openclaw/plugin-sdk");
|
|
6
|
-
const utils_js_1 = require("./utils.js");
|
|
7
|
-
const runtime_js_1 = require("./runtime.js");
|
|
8
|
-
const meta = (0, plugin_sdk_1.getChatChannelMeta)("yunzhijia");
|
|
9
|
-
/**
|
|
10
|
-
* Handle incoming YunzhiJia webhook requests
|
|
11
|
-
*/
|
|
12
|
-
async function handleYunzhiJiaWebhook(req, res, config) {
|
|
13
|
-
try {
|
|
14
|
-
// Read request body
|
|
15
|
-
const buffers = [];
|
|
16
|
-
for await (const chunk of req) {
|
|
17
|
-
buffers.push(chunk);
|
|
18
|
-
}
|
|
19
|
-
const body = node_buffer_1.Buffer.concat(buffers).toString("utf-8");
|
|
20
|
-
// Parse JSON payload
|
|
21
|
-
let payload;
|
|
22
|
-
try {
|
|
23
|
-
payload = JSON.parse(body);
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
console.error("Invalid JSON payload:", error);
|
|
27
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
28
|
-
res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
// Get account configuration (use default account for now)
|
|
32
|
-
const accountId = "default";
|
|
33
|
-
const accountConfig = config.accounts?.[accountId] ?? {
|
|
34
|
-
webhookToken: config.webhookToken,
|
|
35
|
-
appSecret: config.appSecret,
|
|
36
|
-
enabled: config.enabled,
|
|
37
|
-
name: config.name,
|
|
38
|
-
requireMention: config.requireMention ?? true,
|
|
39
|
-
};
|
|
40
|
-
// Verify signature based on skipSignatureVerification config
|
|
41
|
-
const signature = req.headers["sign"];
|
|
42
|
-
const skipSignatureVerification = accountConfig.skipSignatureVerification === true;
|
|
43
|
-
if (!skipSignatureVerification && accountConfig.appSecret && signature) {
|
|
44
|
-
// Note: Cloud之家 uses HmacSHA1 + Base64, but current implementation uses SHA256 + hex
|
|
45
|
-
// Need to verify actual algorithm used by YunzhiJia
|
|
46
|
-
if (!(0, utils_js_1.verifySignature)(body, signature, accountConfig.appSecret)) {
|
|
47
|
-
console.error("Invalid signature");
|
|
48
|
-
res.writeHead(401, { "Content-Type": "application/json" });
|
|
49
|
-
res.end(JSON.stringify({ error: "Invalid signature" }));
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
// Log when signature verification is skipped
|
|
54
|
-
if (skipSignatureVerification) {
|
|
55
|
-
console.warn("YunzhiJia signature verification skipped (skipSignatureVerification=true)");
|
|
56
|
-
}
|
|
57
|
-
// Extract user info
|
|
58
|
-
const { userId, userName, messageId, content } = (0, utils_js_1.extractUserInfo)(payload);
|
|
59
|
-
// Get session ID from header
|
|
60
|
-
const sessionId = req.headers["sessionid"];
|
|
61
|
-
// Immediately respond to meet YunzhiJia's 3-second requirement
|
|
62
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
63
|
-
res.end(JSON.stringify({ success: true, data: { type: 2, content: "" } }));
|
|
64
|
-
// Process message asynchronously using OpenClaw Runtime
|
|
65
|
-
try {
|
|
66
|
-
await processIncomingMessageAsync({
|
|
67
|
-
payload,
|
|
68
|
-
sessionId,
|
|
69
|
-
accountConfig,
|
|
70
|
-
accountId,
|
|
71
|
-
config,
|
|
72
|
-
userId,
|
|
73
|
-
userName,
|
|
74
|
-
messageId,
|
|
75
|
-
content,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
console.error("Async message processing failed:", error);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
console.error("Error handling YunzhiJia webhook:", error);
|
|
84
|
-
if (!res.writableEnded) {
|
|
85
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
86
|
-
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
async function processIncomingMessageAsync(params) {
|
|
91
|
-
const { payload, sessionId, accountConfig, accountId, config, userId, userName, messageId, content } = params;
|
|
92
|
-
const runtime = (0, runtime_js_1.getYunzhiJiaRuntime)();
|
|
93
|
-
// Check mention requirement
|
|
94
|
-
const robotName = String(payload.robotName || "");
|
|
95
|
-
const requireMention = accountConfig.requireMention !== false;
|
|
96
|
-
if (requireMention && !isBotMentioned(content, robotName)) {
|
|
97
|
-
console.debug("Message does not mention bot, ignoring");
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
// Extract clean content (remove bot mention if needed)
|
|
101
|
-
const cleanContent = extractCleanContent(content, robotName);
|
|
102
|
-
// Create session key for conversation tracking
|
|
103
|
-
const sessionKey = createSessionKey(sessionId, String(payload.robotId || ""));
|
|
104
|
-
// Route to appropriate agent
|
|
105
|
-
const route = runtime.channel.routing.resolveAgentRoute({
|
|
106
|
-
cfg: config,
|
|
107
|
-
channel: "yunzhijia",
|
|
108
|
-
accountId,
|
|
109
|
-
peer: {
|
|
110
|
-
kind: "direct",
|
|
111
|
-
id: userId,
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
// Create message context
|
|
115
|
-
const ctxPayload = {
|
|
116
|
-
Body: cleanContent,
|
|
117
|
-
RawBody: content,
|
|
118
|
-
CommandBody: cleanContent,
|
|
119
|
-
From: `yunzhijia:user:${userId}`,
|
|
120
|
-
To: `yunzhijia:robot:${payload.robotId}`,
|
|
121
|
-
SessionKey: sessionKey,
|
|
122
|
-
AccountId: accountId,
|
|
123
|
-
ChatType: "direct",
|
|
124
|
-
ConversationLabel: userName,
|
|
125
|
-
SenderName: userName,
|
|
126
|
-
SenderId: userId,
|
|
127
|
-
Provider: "yunzhijia",
|
|
128
|
-
Surface: "yunzhijia",
|
|
129
|
-
MessageSid: messageId,
|
|
130
|
-
OriginatingChannel: "yunzhijia",
|
|
131
|
-
OriginatingTo: `yunzhijia:robot:${payload.robotId}`,
|
|
132
|
-
Timestamp: payload.time,
|
|
133
|
-
};
|
|
134
|
-
// Record inbound session
|
|
135
|
-
const storePath = runtime.channel.session.resolveStorePath(config.session?.store, {
|
|
136
|
-
agentId: route.agentId,
|
|
137
|
-
});
|
|
138
|
-
await runtime.channel.session.recordInboundSession({
|
|
139
|
-
storePath,
|
|
140
|
-
sessionKey: ctxPayload.SessionKey ?? sessionKey,
|
|
141
|
-
ctx: ctxPayload,
|
|
142
|
-
onRecordError: (err) => {
|
|
143
|
-
console.error(`Failed updating session meta: ${String(err)}`);
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
// Dispatch to agent for processing
|
|
147
|
-
const prefixOptions = createReplyPrefixOptions({
|
|
148
|
-
cfg: config,
|
|
149
|
-
agentId: route.agentId,
|
|
150
|
-
channel: "yunzhijia",
|
|
151
|
-
accountId,
|
|
152
|
-
});
|
|
153
|
-
// Fire-and-forget processing
|
|
154
|
-
void runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
155
|
-
ctx: ctxPayload,
|
|
156
|
-
cfg: config,
|
|
157
|
-
dispatcherOptions: {
|
|
158
|
-
...prefixOptions,
|
|
159
|
-
deliver: async (payload) => {
|
|
160
|
-
if (!payload.text) {
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
await sendWebhookMessage(payload.text, accountConfig.webhookToken);
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
}).catch((err) => {
|
|
167
|
-
console.error(`Message processing failed: ${String(err)}`);
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
// Helper functions that need to be implemented
|
|
171
|
-
function isBotMentioned(content, robotName) {
|
|
172
|
-
// Check if content contains @robotName or similar mention pattern
|
|
173
|
-
return content.includes(`@${robotName}`) || content.trim().startsWith(robotName);
|
|
174
|
-
}
|
|
175
|
-
function extractCleanContent(content, robotName) {
|
|
176
|
-
// Remove bot mention from content
|
|
177
|
-
let clean = content;
|
|
178
|
-
if (robotName) {
|
|
179
|
-
clean = clean.replace(new RegExp(`@?${robotName}\\s*`, 'g'), '').trim();
|
|
180
|
-
}
|
|
181
|
-
return clean;
|
|
182
|
-
}
|
|
183
|
-
function createSessionKey(sessionId, robotId) {
|
|
184
|
-
if (sessionId) {
|
|
185
|
-
return `yunzhijia:${robotId}:${sessionId}`;
|
|
186
|
-
}
|
|
187
|
-
return `yunzhijia:${robotId}:${Date.now()}`;
|
|
188
|
-
}
|
|
189
|
-
async function sendWebhookMessage(content, webhookToken) {
|
|
190
|
-
if (!webhookToken) {
|
|
191
|
-
throw new Error("webhookToken is required");
|
|
192
|
-
}
|
|
193
|
-
const webhookUrl = `https://www.yunzhijia.com/gateway/robot/webhook/send?yzjtype=12&yzjtoken=${webhookToken}`;
|
|
194
|
-
const message = {
|
|
195
|
-
content: content,
|
|
196
|
-
};
|
|
197
|
-
const response = await fetch(webhookUrl, {
|
|
198
|
-
method: 'POST',
|
|
199
|
-
headers: {
|
|
200
|
-
'Content-Type': 'application/json',
|
|
201
|
-
},
|
|
202
|
-
body: JSON.stringify(message),
|
|
203
|
-
});
|
|
204
|
-
if (!response.ok) {
|
|
205
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
function createReplyPrefixOptions(params) {
|
|
209
|
-
// Simplified version - in real implementation this would use proper OpenClaw helpers
|
|
210
|
-
return {
|
|
211
|
-
prefix: "",
|
|
212
|
-
onModelSelected: () => { },
|
|
213
|
-
};
|
|
214
|
-
}
|
package/dist/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { yunzhijiaPlugin } from "./plugin.js";
|
package/dist/index.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.yunzhijiaPlugin = void 0;
|
|
4
|
-
var plugin_js_1 = require("./plugin.js");
|
|
5
|
-
Object.defineProperty(exports, "yunzhijiaPlugin", { enumerable: true, get: function () { return plugin_js_1.yunzhijiaPlugin; } });
|
package/dist/plugin-fixed.d.ts
DELETED
package/dist/plugin-fixed.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { buildChannelConfigSchema, DEFAULT_ACCOUNT_ID, } from "openclaw/plugin-sdk";
|
|
2
|
-
import { getAccountConfig, isAccountConfigured, listAccountIds } from "./config.js";
|
|
3
|
-
// Fixed plugin without getChatChannelMeta dependency
|
|
4
|
-
const yunzhijiaMessageActions = {
|
|
5
|
-
listActions: () => ["send"],
|
|
6
|
-
extractToolSend: () => null,
|
|
7
|
-
handleAction: async ({ action, params, accountId, cfg }) => {
|
|
8
|
-
if (action === "send") {
|
|
9
|
-
const account = getAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID);
|
|
10
|
-
if (!account) {
|
|
11
|
-
throw new Error(`YunzhiJia account not found: ${accountId}`);
|
|
12
|
-
}
|
|
13
|
-
const { sendWebhookMessage } = await import("./webhook.js");
|
|
14
|
-
const content = typeof params.message === "string" ? params.message : "";
|
|
15
|
-
const result = await sendWebhookMessage(content, account.webhookToken);
|
|
16
|
-
return { success: result.success };
|
|
17
|
-
}
|
|
18
|
-
throw new Error(`Action ${action} not supported for YunzhiJia`);
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
export const yunzhijiaPlugin = {
|
|
22
|
-
id: "yunzhijia",
|
|
23
|
-
meta: {
|
|
24
|
-
id: "yunzhijia",
|
|
25
|
-
label: "YunzhiJia",
|
|
26
|
-
selectionLabel: "YunzhiJia (Kingdee)",
|
|
27
|
-
docsPath: "/channels/yunzhijia",
|
|
28
|
-
blurb: "Kingdee YunzhiJia enterprise messaging integration",
|
|
29
|
-
aliases: ["kingdee", "yzj"],
|
|
30
|
-
},
|
|
31
|
-
capabilities: {
|
|
32
|
-
chatTypes: ["direct", "group"],
|
|
33
|
-
media: false,
|
|
34
|
-
},
|
|
35
|
-
configSchema: buildChannelConfigSchema({
|
|
36
|
-
type: "object",
|
|
37
|
-
properties: {
|
|
38
|
-
appSecret: { type: "string" },
|
|
39
|
-
webhookToken: { type: "string" },
|
|
40
|
-
allowFrom: { type: "array", items: { type: "string" } },
|
|
41
|
-
requireMention: { type: "boolean" },
|
|
42
|
-
responsePrefix: { type: "string" },
|
|
43
|
-
},
|
|
44
|
-
required: ["appSecret", "webhookToken"],
|
|
45
|
-
}),
|
|
46
|
-
config: {
|
|
47
|
-
listAccountIds: (cfg) => listAccountIds(cfg),
|
|
48
|
-
resolveAccount: (cfg, accountId) => getAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID) ?? {
|
|
49
|
-
appSecret: "",
|
|
50
|
-
webhookToken: "",
|
|
51
|
-
},
|
|
52
|
-
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
53
|
-
isConfigured: (account) => isAccountConfigured(account),
|
|
54
|
-
describeAccount: (account) => ({
|
|
55
|
-
accountId: DEFAULT_ACCOUNT_ID,
|
|
56
|
-
enabled: account?.enabled !== false,
|
|
57
|
-
configured: isAccountConfigured(account),
|
|
58
|
-
}),
|
|
59
|
-
},
|
|
60
|
-
security: {
|
|
61
|
-
resolveDmPolicy: ({ account }) => ({
|
|
62
|
-
policy: account.requireMention !== false ? "pairing" : "open",
|
|
63
|
-
allowFrom: account.allowFrom ?? [],
|
|
64
|
-
allowFromPath: "channels.yunzhijia.allowFrom",
|
|
65
|
-
normalizeEntry: (raw) => raw.replace(/^(yunzhijia|yzj):/i, ""),
|
|
66
|
-
}),
|
|
67
|
-
},
|
|
68
|
-
mentions: {
|
|
69
|
-
stripPatterns: ({ ctx }) => {
|
|
70
|
-
const robotName = ctx.To?.replace(/^yunzhijia:robot:/, "") || "";
|
|
71
|
-
return [`@${robotName}`];
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
actions: yunzhijiaMessageActions,
|
|
75
|
-
outbound: {
|
|
76
|
-
deliveryMode: "direct",
|
|
77
|
-
textChunkLimit: 2000,
|
|
78
|
-
sendText: async ({ to, text, accountId, cfg }) => {
|
|
79
|
-
const account = getAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID);
|
|
80
|
-
if (!account) {
|
|
81
|
-
throw new Error(`YunzhiJia account not found: ${accountId}`);
|
|
82
|
-
}
|
|
83
|
-
const { sendWebhookMessage } = await import("./webhook.js");
|
|
84
|
-
const result = await sendWebhookMessage(text, account.webhookToken);
|
|
85
|
-
return {
|
|
86
|
-
channel: "yunzhijia",
|
|
87
|
-
messageId: result.messageId,
|
|
88
|
-
timestamp: Date.now(),
|
|
89
|
-
to,
|
|
90
|
-
};
|
|
91
|
-
},
|
|
92
|
-
sendMedia: async ({ to, text, mediaUrl, accountId, cfg }) => {
|
|
93
|
-
const combinedText = mediaUrl ? `${text || ""} ${mediaUrl}`.trim() : text;
|
|
94
|
-
return await yunzhijiaPlugin.outbound.sendText({
|
|
95
|
-
to,
|
|
96
|
-
text: combinedText,
|
|
97
|
-
accountId,
|
|
98
|
-
cfg,
|
|
99
|
-
});
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
gateway: {
|
|
103
|
-
startAccount: async () => { },
|
|
104
|
-
stopAccount: async () => { },
|
|
105
|
-
},
|
|
106
|
-
};
|
package/dist/plugin-simple.d.ts
DELETED