moltbot-wecom 1.0.2 → 2.0.0
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.
- package/clawdbot.plugin.json +42 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/src/accounts.d.ts +15 -0
- package/dist/src/accounts.d.ts.map +1 -0
- package/dist/src/accounts.js +90 -0
- package/dist/src/channel.d.ts +8 -0
- package/dist/src/channel.d.ts.map +1 -0
- package/dist/src/channel.js +365 -0
- package/dist/src/config-schema.d.ts +211 -0
- package/dist/src/config-schema.d.ts.map +1 -0
- package/dist/src/config-schema.js +21 -0
- package/dist/src/dedup.d.ts +8 -0
- package/dist/src/dedup.d.ts.map +1 -0
- package/dist/src/dedup.js +24 -0
- package/dist/src/group-filter.d.ts +15 -0
- package/dist/src/group-filter.d.ts.map +1 -0
- package/dist/src/group-filter.js +43 -0
- package/dist/src/probe.d.ts +10 -0
- package/dist/src/probe.d.ts.map +1 -0
- package/dist/src/probe.js +70 -0
- package/dist/src/receive.d.ts +26 -0
- package/dist/src/receive.d.ts.map +1 -0
- package/dist/src/receive.js +282 -0
- package/dist/src/runtime.d.ts +7 -0
- package/dist/src/runtime.d.ts.map +1 -0
- package/dist/src/runtime.js +13 -0
- package/dist/src/send.d.ts +26 -0
- package/dist/src/send.d.ts.map +1 -0
- package/dist/src/send.js +75 -0
- package/dist/src/status-issues.d.ts +7 -0
- package/dist/src/status-issues.d.ts.map +1 -0
- package/dist/src/status-issues.js +47 -0
- package/dist/src/types.d.ts +79 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +4 -0
- package/package.json +28 -11
- package/moltbot.plugin.json +0 -27
- package/plugin.js +0 -243
package/plugin.js
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
const WebSocket = require('ws');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Moltbot WeCom Channel Plugin
|
|
5
|
-
*
|
|
6
|
-
* Config in moltbot.json:
|
|
7
|
-
* "channels": {
|
|
8
|
-
* "wecom": {
|
|
9
|
-
* "enabled": true,
|
|
10
|
-
* "proxyUrl": "wss://...",
|
|
11
|
-
* "proxyToken": "...",
|
|
12
|
-
* }
|
|
13
|
-
* }
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
module.exports = function(ctx) {
|
|
17
|
-
const log = ctx.logger ? ctx.logger('wecom') : console;
|
|
18
|
-
const cfg = ctx.config?.channels?.wecom || {};
|
|
19
|
-
|
|
20
|
-
if (!cfg.enabled) {
|
|
21
|
-
log.info('WeCom channel disabled');
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const PROXY_URL = cfg.proxyUrl || process.env.PROXY_URL;
|
|
26
|
-
const PROXY_TOKEN = cfg.proxyToken || process.env.PROXY_TOKEN;
|
|
27
|
-
const PING_INTERVAL_MS = cfg.pingInterval || 30000; // 30s heartbeat
|
|
28
|
-
|
|
29
|
-
if (!PROXY_URL) {
|
|
30
|
-
log.error('Missing proxyUrl for WeCom channel');
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// --- State & Dedup (Reference: feishu-moltbot-bridge) ---
|
|
35
|
-
const seen = new Map();
|
|
36
|
-
const SEEN_TTL = 10 * 60 * 1000;
|
|
37
|
-
|
|
38
|
-
function isDuplicate(id) {
|
|
39
|
-
if (!id) return false;
|
|
40
|
-
const now = Date.now();
|
|
41
|
-
for (const [k, ts] of seen) {
|
|
42
|
-
if (now - ts > SEEN_TTL) seen.delete(k);
|
|
43
|
-
}
|
|
44
|
-
if (seen.has(id)) return true;
|
|
45
|
-
seen.set(id, now);
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// --- Group Logic (Reference: feishu-moltbot-bridge) ---
|
|
50
|
-
function shouldRespond(text, isGroup, mentions = []) {
|
|
51
|
-
if (!isGroup) return true; // Always respond to DM
|
|
52
|
-
|
|
53
|
-
// 1. Mentions (Smart Bot usually only receives @bot messages, but check anyway)
|
|
54
|
-
if (mentions.length > 0) return true;
|
|
55
|
-
|
|
56
|
-
// 2. Keywords/Questions
|
|
57
|
-
const t = text.toLowerCase();
|
|
58
|
-
if (/[??]$/.test(text)) return true;
|
|
59
|
-
if (/\b(why|how|what|when|where|who|help)\b/.test(t)) return true;
|
|
60
|
-
|
|
61
|
-
const triggers = ['help', 'bot', '小雀', '助手', 'moltbot'];
|
|
62
|
-
if (triggers.some(k => t.includes(k))) return true;
|
|
63
|
-
|
|
64
|
-
const verbs = ['帮', '请', '查', '分析', '总结', '翻译', '写', '改', '看看'];
|
|
65
|
-
if (verbs.some(k => t.includes(k))) return true;
|
|
66
|
-
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// --- Connection ---
|
|
71
|
-
let ws;
|
|
72
|
-
let reconnectTimer;
|
|
73
|
-
let pingTimer;
|
|
74
|
-
let isAlive = false;
|
|
75
|
-
|
|
76
|
-
function connect() {
|
|
77
|
-
const url = `${PROXY_URL}?token=${PROXY_TOKEN || ''}`;
|
|
78
|
-
log.info(`Connecting to WeCom Proxy: ${PROXY_URL}`);
|
|
79
|
-
|
|
80
|
-
ws = new WebSocket(url);
|
|
81
|
-
|
|
82
|
-
ws.on('open', () => {
|
|
83
|
-
log.info('WeCom Proxy connected');
|
|
84
|
-
isAlive = true;
|
|
85
|
-
if (reconnectTimer) {
|
|
86
|
-
clearTimeout(reconnectTimer);
|
|
87
|
-
reconnectTimer = null;
|
|
88
|
-
}
|
|
89
|
-
// Start heartbeat
|
|
90
|
-
startHeartbeat();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
ws.on('message', async (data) => {
|
|
94
|
-
try {
|
|
95
|
-
const event = JSON.parse(data);
|
|
96
|
-
|
|
97
|
-
// Handle pong response
|
|
98
|
-
if (event.kind === 'pong') {
|
|
99
|
-
isAlive = true;
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (event.kind !== 'message') return;
|
|
104
|
-
|
|
105
|
-
const msg = event.payload;
|
|
106
|
-
const msgId = event.msgId;
|
|
107
|
-
|
|
108
|
-
// Basic validation
|
|
109
|
-
if (!msg || !msg.FromUserName || !msg.Content) {
|
|
110
|
-
// Send empty reply for non-text messages
|
|
111
|
-
sendReply(msgId, '');
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const senderId = msg.FromUserName;
|
|
116
|
-
const text = msg.Content;
|
|
117
|
-
|
|
118
|
-
// Dedup
|
|
119
|
-
if (isDuplicate(msgId)) {
|
|
120
|
-
log.debug(`Duplicate message ignored: ${msgId}`);
|
|
121
|
-
sendReply(msgId, '');
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Group logic - Smart Bot usually only receives relevant messages
|
|
126
|
-
// but we keep the logic for safety
|
|
127
|
-
const isGroup = false; // Smart Bot messages are treated as direct
|
|
128
|
-
|
|
129
|
-
if (isGroup && !shouldRespond(text, true)) {
|
|
130
|
-
log.debug('Ignoring group message (no trigger)');
|
|
131
|
-
sendReply(msgId, '');
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
log.info(`Rx [${senderId}]: ${text}`);
|
|
136
|
-
|
|
137
|
-
// Process message and get reply
|
|
138
|
-
try {
|
|
139
|
-
const replyText = await processMessage(senderId, text, msgId);
|
|
140
|
-
sendReply(msgId, replyText || '');
|
|
141
|
-
} catch (e) {
|
|
142
|
-
log.error('Error processing message:', e);
|
|
143
|
-
sendReply(msgId, '');
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
} catch (e) {
|
|
147
|
-
log.error('Error handling message', e);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
ws.on('close', () => {
|
|
152
|
-
log.warn('WeCom Proxy disconnected');
|
|
153
|
-
isAlive = false;
|
|
154
|
-
stopHeartbeat();
|
|
155
|
-
scheduleReconnect();
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
ws.on('error', (e) => {
|
|
159
|
-
log.error('WeCom connection error:', e.message);
|
|
160
|
-
ws.terminate();
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// --- Reply to Proxy ---
|
|
165
|
-
function sendReply(msgId, text) {
|
|
166
|
-
if (ws && ws.readyState === WebSocket.OPEN && msgId) {
|
|
167
|
-
ws.send(JSON.stringify({
|
|
168
|
-
kind: 'reply',
|
|
169
|
-
msgId: msgId,
|
|
170
|
-
text: text
|
|
171
|
-
}));
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// --- Process message through Moltbot ---
|
|
176
|
-
async function processMessage(senderId, text, msgId) {
|
|
177
|
-
return new Promise((resolve, reject) => {
|
|
178
|
-
// Send to Moltbot Pipeline via ctx.receive
|
|
179
|
-
ctx.receive('wecom', {
|
|
180
|
-
from: senderId,
|
|
181
|
-
text: text,
|
|
182
|
-
msgId: msgId,
|
|
183
|
-
// The 'conn' object allows Moltbot to reply
|
|
184
|
-
conn: {
|
|
185
|
-
send: async (replyText) => {
|
|
186
|
-
if (!replyText || replyText === 'NO_REPLY') {
|
|
187
|
-
resolve('');
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
log.info(`Tx [${senderId}]: ${replyText.slice(0, 50)}...`);
|
|
191
|
-
resolve(replyText);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}).catch(e => {
|
|
195
|
-
log.error('Pipeline error:', e);
|
|
196
|
-
resolve('');
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
// Timeout fallback (Moltbot should respond quickly for passive reply)
|
|
200
|
-
setTimeout(() => resolve(''), 4000);
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// --- Heartbeat ---
|
|
205
|
-
function startHeartbeat() {
|
|
206
|
-
stopHeartbeat();
|
|
207
|
-
pingTimer = setInterval(() => {
|
|
208
|
-
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
209
|
-
if (!isAlive) {
|
|
210
|
-
log.warn('Heartbeat timeout, reconnecting...');
|
|
211
|
-
ws.terminate();
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
isAlive = false;
|
|
215
|
-
ws.send(JSON.stringify({ kind: 'ping', ts: Date.now() }));
|
|
216
|
-
}
|
|
217
|
-
}, PING_INTERVAL_MS);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function stopHeartbeat() {
|
|
221
|
-
if (pingTimer) {
|
|
222
|
-
clearInterval(pingTimer);
|
|
223
|
-
pingTimer = null;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function scheduleReconnect() {
|
|
228
|
-
if (reconnectTimer) return;
|
|
229
|
-
reconnectTimer = setTimeout(() => {
|
|
230
|
-
reconnectTimer = null;
|
|
231
|
-
connect();
|
|
232
|
-
}, 5000);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// --- Lifecycle ---
|
|
236
|
-
ctx.on('start', () => connect());
|
|
237
|
-
|
|
238
|
-
ctx.on('stop', () => {
|
|
239
|
-
stopHeartbeat();
|
|
240
|
-
if (ws) ws.close();
|
|
241
|
-
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
242
|
-
});
|
|
243
|
-
};
|