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.
Files changed (40) hide show
  1. package/clawdbot.plugin.json +42 -0
  2. package/dist/index.d.ts +29 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +31 -0
  5. package/dist/src/accounts.d.ts +15 -0
  6. package/dist/src/accounts.d.ts.map +1 -0
  7. package/dist/src/accounts.js +90 -0
  8. package/dist/src/channel.d.ts +8 -0
  9. package/dist/src/channel.d.ts.map +1 -0
  10. package/dist/src/channel.js +365 -0
  11. package/dist/src/config-schema.d.ts +211 -0
  12. package/dist/src/config-schema.d.ts.map +1 -0
  13. package/dist/src/config-schema.js +21 -0
  14. package/dist/src/dedup.d.ts +8 -0
  15. package/dist/src/dedup.d.ts.map +1 -0
  16. package/dist/src/dedup.js +24 -0
  17. package/dist/src/group-filter.d.ts +15 -0
  18. package/dist/src/group-filter.d.ts.map +1 -0
  19. package/dist/src/group-filter.js +43 -0
  20. package/dist/src/probe.d.ts +10 -0
  21. package/dist/src/probe.d.ts.map +1 -0
  22. package/dist/src/probe.js +70 -0
  23. package/dist/src/receive.d.ts +26 -0
  24. package/dist/src/receive.d.ts.map +1 -0
  25. package/dist/src/receive.js +282 -0
  26. package/dist/src/runtime.d.ts +7 -0
  27. package/dist/src/runtime.d.ts.map +1 -0
  28. package/dist/src/runtime.js +13 -0
  29. package/dist/src/send.d.ts +26 -0
  30. package/dist/src/send.d.ts.map +1 -0
  31. package/dist/src/send.js +75 -0
  32. package/dist/src/status-issues.d.ts +7 -0
  33. package/dist/src/status-issues.d.ts.map +1 -0
  34. package/dist/src/status-issues.js +47 -0
  35. package/dist/src/types.d.ts +79 -0
  36. package/dist/src/types.d.ts.map +1 -0
  37. package/dist/src/types.js +4 -0
  38. package/package.json +28 -11
  39. package/moltbot.plugin.json +0 -27
  40. 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
- };