@jackle.dev/zalox-plugin 1.0.19 → 1.0.21
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/package.json +1 -1
- package/src/listener.ts +26 -34
package/package.json
CHANGED
package/src/listener.ts
CHANGED
|
@@ -62,7 +62,6 @@ async function downloadZaloMedia(url: string, profile: string, filename?: string
|
|
|
62
62
|
const credentialsPath = resolveCredentialsPath(profile);
|
|
63
63
|
const creds = JSON.parse(readFileSync(credentialsPath, 'utf-8'));
|
|
64
64
|
|
|
65
|
-
// Zalo media URLs usually require cookies
|
|
66
65
|
const headers: Record<string, string> = {
|
|
67
66
|
'User-Agent': creds.userAgent || 'Mozilla/5.0',
|
|
68
67
|
'Cookie': typeof creds.cookie === 'object' ? JSON.stringify(creds.cookie) : String(creds.cookie),
|
|
@@ -77,7 +76,6 @@ async function downloadZaloMedia(url: string, profile: string, filename?: string
|
|
|
77
76
|
const contentType = res.headers.get('content-type');
|
|
78
77
|
const buffer = await res.arrayBuffer();
|
|
79
78
|
|
|
80
|
-
// Determine extension
|
|
81
79
|
let ext = extname(url).split('?')[0];
|
|
82
80
|
if (!ext || ext.length > 5) {
|
|
83
81
|
if (contentType?.includes('jpeg')) ext = '.jpg';
|
|
@@ -119,13 +117,11 @@ async function processMessage(
|
|
|
119
117
|
const core = getRuntime();
|
|
120
118
|
const { threadId, content, msgType, timestamp, isGroup, senderId, senderName, groupName, mediaUrl } = message;
|
|
121
119
|
|
|
122
|
-
// Allow empty content if it's an image
|
|
123
120
|
if (!content?.trim() && !mediaUrl) return;
|
|
124
121
|
|
|
125
122
|
const chatId = threadId;
|
|
126
123
|
const rawBody = content.trim();
|
|
127
124
|
|
|
128
|
-
// Handle media download
|
|
129
125
|
let mediaPath: string | undefined;
|
|
130
126
|
if (mediaUrl) {
|
|
131
127
|
const downloaded = await downloadZaloMedia(mediaUrl, profile);
|
|
@@ -137,7 +133,6 @@ async function processMessage(
|
|
|
137
133
|
}
|
|
138
134
|
}
|
|
139
135
|
|
|
140
|
-
// DM policy check
|
|
141
136
|
const configAllowFrom = (account.config.allowFrom ?? []).map(String);
|
|
142
137
|
const dmPolicy = account.config.dmPolicy ?? 'pairing';
|
|
143
138
|
|
|
@@ -194,7 +189,6 @@ async function processMessage(
|
|
|
194
189
|
}
|
|
195
190
|
}
|
|
196
191
|
|
|
197
|
-
// Group command authorization
|
|
198
192
|
if (
|
|
199
193
|
isGroup &&
|
|
200
194
|
core.channel.commands.isControlCommandMessage(rawBody, config) &&
|
|
@@ -203,7 +197,6 @@ async function processMessage(
|
|
|
203
197
|
return;
|
|
204
198
|
}
|
|
205
199
|
|
|
206
|
-
// Route to agent
|
|
207
200
|
const peer = isGroup
|
|
208
201
|
? { kind: 'group' as const, id: chatId }
|
|
209
202
|
: { kind: 'dm' as const, id: senderId };
|
|
@@ -251,7 +244,7 @@ async function processMessage(
|
|
|
251
244
|
MessageSid: message.msgId ?? `${timestamp}`,
|
|
252
245
|
OriginatingChannel: 'zalox',
|
|
253
246
|
OriginatingTo: `zalox:${chatId}`,
|
|
254
|
-
MediaPath: mediaPath,
|
|
247
|
+
MediaPath: mediaPath,
|
|
255
248
|
});
|
|
256
249
|
|
|
257
250
|
await core.channel.session.recordInboundSession({
|
|
@@ -304,7 +297,6 @@ export async function startInProcessListener(
|
|
|
304
297
|
|
|
305
298
|
const listener = api.listener;
|
|
306
299
|
|
|
307
|
-
// Handle incoming messages
|
|
308
300
|
listener.on('message', (msg: any) => {
|
|
309
301
|
if (stopped || abortSignal.aborted) return;
|
|
310
302
|
|
|
@@ -312,7 +304,6 @@ export async function startInProcessListener(
|
|
|
312
304
|
const isGroup = msg.type === 1;
|
|
313
305
|
const senderId = data.uidFrom ? String(data.uidFrom) : '';
|
|
314
306
|
|
|
315
|
-
// Skip own messages
|
|
316
307
|
if (senderId === ownId) return;
|
|
317
308
|
|
|
318
309
|
const threadId = isGroup
|
|
@@ -321,17 +312,11 @@ export async function startInProcessListener(
|
|
|
321
312
|
|
|
322
313
|
const msgType = data.msgType || 0;
|
|
323
314
|
|
|
324
|
-
// DEBUG: Force reply details for every DM to user (DISABLED)
|
|
325
|
-
// if (!isGroup) { ... }
|
|
326
|
-
|
|
327
315
|
let mediaUrl = undefined;
|
|
328
|
-
// Prioritize high-res URL if available
|
|
329
316
|
mediaUrl = data.url || data.href;
|
|
330
317
|
if (!mediaUrl && (msgType === 2 || String(msgType) === 'chat.photo')) {
|
|
331
|
-
// Try to find URL in params or content
|
|
332
318
|
if (data.params?.url) mediaUrl = data.params.url;
|
|
333
319
|
else if (typeof data.content === 'object' && data.content !== null) {
|
|
334
|
-
// Already parsed object
|
|
335
320
|
mediaUrl = (data.content as any).href || (data.content as any).url || (data.content as any).thumb || (data.content as any).normalUrl;
|
|
336
321
|
}
|
|
337
322
|
else if (typeof data.content === 'string' && (data.content.includes('http') || data.content.startsWith('{'))) {
|
|
@@ -339,17 +324,14 @@ export async function startInProcessListener(
|
|
|
339
324
|
const parsed = JSON.parse(data.content);
|
|
340
325
|
mediaUrl = parsed.href || parsed.url || parsed.thumb || parsed.normalUrl;
|
|
341
326
|
} catch {
|
|
342
|
-
// Maybe content IS the url?
|
|
343
327
|
if (data.content.startsWith('http')) mediaUrl = data.content;
|
|
344
328
|
}
|
|
345
329
|
}
|
|
346
330
|
}
|
|
347
|
-
// Also check for image extension if URL exists but type != 2
|
|
348
331
|
if (!mediaUrl && data.url && (data.url.includes('.jpg') || data.url.includes('.png'))) {
|
|
349
332
|
mediaUrl = data.url;
|
|
350
333
|
}
|
|
351
334
|
|
|
352
|
-
// Check quote/reply for media
|
|
353
335
|
if (!mediaUrl && data.quote) {
|
|
354
336
|
try {
|
|
355
337
|
const q = data.quote;
|
|
@@ -360,11 +342,9 @@ export async function startInProcessListener(
|
|
|
360
342
|
if (!mediaUrl && (q.href || q.url)) {
|
|
361
343
|
mediaUrl = q.href || q.url;
|
|
362
344
|
}
|
|
363
|
-
} catch {}
|
|
345
|
+
} catch {}
|
|
364
346
|
}
|
|
365
347
|
|
|
366
|
-
// Group messages: only respond when bot is mentioned (@tagged)
|
|
367
|
-
// UNLESS it's a media message — we might want to analyze all images?
|
|
368
348
|
if (isGroup) {
|
|
369
349
|
const mentions = data.mentions || data.mentionIds || [];
|
|
370
350
|
const content = typeof data.content === 'string' ? data.content : (data.msg || '');
|
|
@@ -373,12 +353,17 @@ export async function startInProcessListener(
|
|
|
373
353
|
? mentions.some((m: any) => String(m.uid || m.id || m) === ownId)
|
|
374
354
|
: false;
|
|
375
355
|
const name = account.name || 'Bot';
|
|
376
|
-
const textMention = content.includes(`@${name}`) || content.includes('@Tiệp Lê');
|
|
377
356
|
|
|
378
|
-
|
|
379
|
-
|
|
357
|
+
const textMention =
|
|
358
|
+
content.includes(`@${name}`) ||
|
|
359
|
+
content.includes('@Bot') ||
|
|
360
|
+
content.includes('@Javis');
|
|
361
|
+
|
|
362
|
+
console.log(`[ZaloX] Group Check: ownId=${ownId} mentions=${JSON.stringify(mentions)} textMention=${textMention} content="${content.slice(0, 50)}..."`);
|
|
380
363
|
|
|
381
|
-
if (
|
|
364
|
+
if (content.trim() === '/whoami') {
|
|
365
|
+
// Pass
|
|
366
|
+
} else if (!isMentioned && !textMention && !content.startsWith('/')) {
|
|
382
367
|
return;
|
|
383
368
|
}
|
|
384
369
|
}
|
|
@@ -393,10 +378,10 @@ export async function startInProcessListener(
|
|
|
393
378
|
if (typeof c !== 'string') return '';
|
|
394
379
|
if (isGroup) {
|
|
395
380
|
const name = account.name || 'Bot';
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
c = c.
|
|
381
|
+
// SAFE CLEANUP: No Regex
|
|
382
|
+
if (c.includes(`@${name}`)) c = c.split(`@${name}`).join('').trim();
|
|
383
|
+
if (c.includes('@Javis')) c = c.split('@Javis').join('').trim();
|
|
384
|
+
if (c.includes('@Bot')) c = c.split('@Bot').join('').trim();
|
|
400
385
|
}
|
|
401
386
|
return c;
|
|
402
387
|
})(),
|
|
@@ -411,20 +396,27 @@ export async function startInProcessListener(
|
|
|
411
396
|
|
|
412
397
|
if (!normalized.content?.trim() && !normalized.mediaUrl) return;
|
|
413
398
|
|
|
414
|
-
|
|
415
|
-
|
|
399
|
+
try {
|
|
400
|
+
const reactThreadType = isGroup ? ThreadType.Group : ThreadType.User;
|
|
401
|
+
api.addReaction(Reactions.HEART, {
|
|
402
|
+
threadId: normalized.threadId,
|
|
403
|
+
type: reactThreadType,
|
|
404
|
+
data: {
|
|
405
|
+
msgId: String(data.msgId || ''),
|
|
406
|
+
cliMsgId: String(data.cliMsgId || ''),
|
|
407
|
+
},
|
|
408
|
+
}).catch(() => {});
|
|
409
|
+
} catch {}
|
|
416
410
|
|
|
417
411
|
processMessage(normalized, account, config, runtime, profile, statusSink).catch((err) => {
|
|
418
412
|
runtime.error?.(`[${account.accountId}] ZaloX process error: ${String(err)}`);
|
|
419
413
|
});
|
|
420
414
|
});
|
|
421
415
|
|
|
422
|
-
// Handle errors
|
|
423
416
|
listener.on('error', (err: any) => {
|
|
424
417
|
runtime.error?.(`[${account.accountId}] ZaloX listener error: ${err.message || err}`);
|
|
425
418
|
});
|
|
426
419
|
|
|
427
|
-
// Start WebSocket listener with retry
|
|
428
420
|
listener.start({ retryOnClose: true });
|
|
429
421
|
runtime.log?.(`[${account.accountId}] ZaloX: WebSocket listener started`);
|
|
430
422
|
|