@zzedbot/yunzhijia 1.0.2
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/ALTERNATIVE_INSTALLATION.md +61 -0
- package/DEPLOYMENT.md +380 -0
- package/DEVELOPMENT_REQUIREMENTS.md +374 -0
- package/PUBLISHING_INSTRUCTIONS.md +165 -0
- package/README.md +43 -0
- package/example-config-secure.yaml +13 -0
- package/example-config.yaml +12 -0
- package/index.js +6 -0
- package/index.ts +19 -0
- package/integration-test.cjs +231 -0
- package/openclaw-yunzhijia-2026.2.9-1.tgz +0 -0
- package/openclaw.plugin.json +26 -0
- package/package.json +51 -0
- package/src/config.ts +28 -0
- package/src/http.ts +256 -0
- package/src/index.ts +1 -0
- package/src/plugin-simple.ts +109 -0
- package/src/plugin.ts +291 -0
- package/src/receiver.ts +130 -0
- package/src/runtime.ts +17 -0
- package/src/types.ts +54 -0
- package/src/utils.ts +119 -0
- package/src/webhook.ts +50 -0
- package/test-import.mjs +3 -0
- package/tsconfig.json +17 -0
- package/zzedbot-yunzhijia-1.0.1.tgz +0 -0
- package/zzedbot-yunzhijia-1.0.2.tgz +0 -0
package/src/utils.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import type { YunzhiJiaAccountConfig } from "./types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Verify YunzhiJia webhook signature using HmacSHA1 + Base64 (as per YunzhiJia documentation)
|
|
6
|
+
* Note: YunzhiJia documentation shows HmacSHA1 + Base64, but some implementations may use SHA256 + hex
|
|
7
|
+
* We'll implement both and allow configuration to choose
|
|
8
|
+
*/
|
|
9
|
+
export function verifySignature(
|
|
10
|
+
body: string,
|
|
11
|
+
signature: string,
|
|
12
|
+
appSecret: string,
|
|
13
|
+
skipSignatureVerification?: boolean,
|
|
14
|
+
): boolean {
|
|
15
|
+
// Explicitly skip signature verification if configured
|
|
16
|
+
if (skipSignatureVerification) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// If no app secret configured, skip verification (for testing)
|
|
21
|
+
if (!appSecret) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Parse the JSON body to extract fields for signature calculation
|
|
26
|
+
try {
|
|
27
|
+
const payload = JSON.parse(body);
|
|
28
|
+
const summaryInfo = [
|
|
29
|
+
payload.robotId,
|
|
30
|
+
payload.robotName,
|
|
31
|
+
payload.operatorOpenid,
|
|
32
|
+
payload.operatorName,
|
|
33
|
+
payload.time?.toString(),
|
|
34
|
+
payload.msgId,
|
|
35
|
+
payload.content
|
|
36
|
+
].join(",");
|
|
37
|
+
|
|
38
|
+
// Try HmacSHA1 + Base64 first (as per YunzhiJia documentation)
|
|
39
|
+
const hmacSha1 = createHmac('sha1', appSecret);
|
|
40
|
+
hmacSha1.update(summaryInfo);
|
|
41
|
+
const expectedSignatureBase64 = hmacSha1.digest('base64');
|
|
42
|
+
|
|
43
|
+
if (signature === expectedSignatureBase64) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Fallback to SHA256 + hex (current implementation)
|
|
48
|
+
const expectedSignatureSha256 = createHash("sha256")
|
|
49
|
+
.update(body + appSecret)
|
|
50
|
+
.digest("hex");
|
|
51
|
+
|
|
52
|
+
return signature === expectedSignatureSha256;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error("Error parsing payload for signature verification:", error);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create HMAC hash (Node.js compatible)
|
|
61
|
+
*/
|
|
62
|
+
function createHmac(algorithm: string, key: string) {
|
|
63
|
+
const crypto = require('node:crypto');
|
|
64
|
+
return crypto.createHmac(algorithm, key);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Format message for YunzhiJia webhook
|
|
69
|
+
*/
|
|
70
|
+
export function formatYunzhiJiaMessage(content: string): Record<string, unknown> {
|
|
71
|
+
return {
|
|
72
|
+
content: content,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Extract user info from YunzhiJia webhook payload
|
|
78
|
+
*/
|
|
79
|
+
export function extractUserInfo(payload: Record<string, unknown>) {
|
|
80
|
+
return {
|
|
81
|
+
userId: String(payload.operatorOpenid ?? "unknown"),
|
|
82
|
+
userName: String(payload.operatorName ?? "Unknown User"),
|
|
83
|
+
messageId: String(payload.msgId ?? Date.now().toString()),
|
|
84
|
+
content: String(payload.content ?? ""),
|
|
85
|
+
robotId: String(payload.robotId ?? "unknown"),
|
|
86
|
+
robotName: String(payload.robotName ?? "YunzhiJia Bot"),
|
|
87
|
+
time: Number(payload.time ?? Date.now()),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check if bot is mentioned in the message content
|
|
93
|
+
*/
|
|
94
|
+
export function isBotMentioned(content: string, robotName: string): boolean {
|
|
95
|
+
// YunzhiJia mentions are typically in the format "@BotName"
|
|
96
|
+
const mentionPattern = new RegExp(`@${robotName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'i');
|
|
97
|
+
return mentionPattern.test(content) || content.includes('@ALL') || content.includes('@all');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Extract clean content by removing bot mention
|
|
102
|
+
*/
|
|
103
|
+
export function extractCleanContent(content: string, robotName: string): string {
|
|
104
|
+
// Remove bot mention patterns
|
|
105
|
+
const cleaned = content
|
|
106
|
+
.replace(new RegExp(`@${robotName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'gi'), '')
|
|
107
|
+
.replace(/@ALL/gi, '')
|
|
108
|
+
.replace(/@all/gi, '')
|
|
109
|
+
.trim();
|
|
110
|
+
|
|
111
|
+
return cleaned || content; // Return original if nothing was cleaned
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Create session key from sessionId and robotId
|
|
116
|
+
*/
|
|
117
|
+
export function createSessionKey(sessionId: string, robotId: string): string {
|
|
118
|
+
return `yunzhijia:${robotId}:${sessionId}`;
|
|
119
|
+
}
|
package/src/webhook.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { YunzhiJiaAccountConfig } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Send message to YunzhiJia webhook
|
|
5
|
+
*/
|
|
6
|
+
export async function sendToYunzhiJiaWebhook(
|
|
7
|
+
content: string,
|
|
8
|
+
accountConfig: YunzhiJiaAccountConfig,
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
if (!accountConfig.webhookToken) {
|
|
11
|
+
throw new Error("webhookToken is required");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const webhookUrl = `https://www.yunzhijia.com/gateway/robot/webhook/send?yzjtype=12&yzjtoken=${accountConfig.webhookToken}`;
|
|
15
|
+
const message = {
|
|
16
|
+
content: content,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Use the runtime's fetch implementation for proper error handling and proxy support
|
|
21
|
+
const response = await fetch(webhookUrl, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify(message),
|
|
27
|
+
timeout: 10000,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error("Failed to send message to YunzhiJia:", error);
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Send media to YunzhiJia (placeholder - YunzhiJia may not support media directly)
|
|
41
|
+
*/
|
|
42
|
+
export async function sendMediaToYunzhiJia(
|
|
43
|
+
content: string,
|
|
44
|
+
mediaUrl: string,
|
|
45
|
+
accountConfig: YunzhiJiaAccountConfig,
|
|
46
|
+
): Promise<void> {
|
|
47
|
+
// For now, just send the content with media URL as text
|
|
48
|
+
const messageWithMedia = `${content}\n${mediaUrl}`;
|
|
49
|
+
await sendToYunzhiJiaWebhook(messageWithMedia, accountConfig);
|
|
50
|
+
}
|
package/test-import.mjs
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"rootDir": "./src",
|
|
13
|
+
"types": ["node"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|
|
Binary file
|
|
Binary file
|