n8n-nodes-zalo-custom 1.0.9 → 1.0.10
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/README.md +2 -1
- package/nodes/ZaloCommunication/ZaloCommunication.node.js +6 -6
- package/nodes/ZaloGroup/ZaloGroup.node.js +9 -4
- package/nodes/ZaloLoginByQr/ZaloLoginByQr.node.js +37 -52
- package/nodes/ZaloSendMessage/ZaloSendMessage.node.js +0 -7
- package/nodes/ZaloTrigger/ZaloTrigger.node.js +33 -23
- package/nodes/ZaloUser/ZaloUser.node.js +9 -4
- package/nodes/utils/zalo.helper.js +29 -7
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -159,7 +159,7 @@ Khi bạn quản lý nhiều tài khoản Zalo, mỗi tài khoản sẽ có mộ
|
|
|
159
159
|
<details>
|
|
160
160
|
<summary><b>nhận sự kiện từ Zalo để phản hồi</b></summary>
|
|
161
161
|
|
|
162
|
-
- **Lắng nghe sự kiện**: tin nhắn người dùng/nhóm, thả tim, thu hồi tin nhắn, lời mời kết bạn.
|
|
162
|
+
- **Lắng nghe sự kiện**: tin nhắn người dùng/nhóm, thả tim, thu hồi tin nhắn, lời mời kết bạn, đã xem tin nhắn, đang soạn tin.
|
|
163
163
|
- **Cấu hình**:
|
|
164
164
|
- Chấp nhận hoặc loại trừ các ID nhóm khi nhận sự kiện tin nhắn.
|
|
165
165
|
- Chỉ nhận theo từ khoá hoặc loại trừ khi nhận sự kiện tin nhắn.
|
|
@@ -200,6 +200,7 @@ Khi bạn quản lý nhiều tài khoản Zalo, mỗi tài khoản sẽ có mộ
|
|
|
200
200
|
- **Tương tác với người dùng:** Thu hồi tin nhắn, chặn/bỏ chặn, đổi tên gợi nhớ, đánh dấu đã đọc/chưa đọc.
|
|
201
201
|
- **Lấy thông tin:** Lấy danh sách bạn bè, gợi ý kết bạn, thông tin chi tiết người dùng (qua User ID/SĐT), lấy mã QR.
|
|
202
202
|
- **Cập nhật hồ sơ:** Thay đổi thông tin cá nhân của bạn (tên, ngày sinh, giới tính).
|
|
203
|
+
- **Lấy tin nhắn cũ:** Lấy danh sách các tin nhắn cũ trong khả năng.
|
|
203
204
|
</details>
|
|
204
205
|
|
|
205
206
|
---
|
|
@@ -41,18 +41,18 @@ class ZaloCommunication {
|
|
|
41
41
|
|
|
42
42
|
try {
|
|
43
43
|
api = await (0, zalo_helper_1.getZaloApiClient)(this);
|
|
44
|
+
if (!api) {
|
|
45
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to initialize Zalo API. Check credentials or User ID.');
|
|
46
|
+
}
|
|
44
47
|
}
|
|
45
48
|
catch (error) {
|
|
46
49
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Zalo login error: ${error.message}`);
|
|
47
50
|
}
|
|
48
|
-
if (!api) {
|
|
49
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No API instance found. Please make sure to provide valid credentials.');
|
|
50
|
-
}
|
|
51
51
|
|
|
52
52
|
for (let i = 0; i < items.length; i++) {
|
|
53
53
|
try {
|
|
54
54
|
switch (resource) {
|
|
55
|
-
// ---
|
|
55
|
+
// --- Sticker ---
|
|
56
56
|
case 'sticker':
|
|
57
57
|
switch (operation) {
|
|
58
58
|
case 'getStickers': {
|
|
@@ -122,7 +122,7 @@ class ZaloCommunication {
|
|
|
122
122
|
}
|
|
123
123
|
break;
|
|
124
124
|
|
|
125
|
-
// ---
|
|
125
|
+
// --- Poll ---
|
|
126
126
|
case 'poll':
|
|
127
127
|
switch (operation) {
|
|
128
128
|
case 'createPoll': {
|
|
@@ -223,7 +223,7 @@ class ZaloCommunication {
|
|
|
223
223
|
}
|
|
224
224
|
break;
|
|
225
225
|
|
|
226
|
-
// ---
|
|
226
|
+
// --- Tag ---
|
|
227
227
|
case 'tag':
|
|
228
228
|
switch (operation) {
|
|
229
229
|
case 'list': {
|
|
@@ -53,16 +53,21 @@ class ZaloGroup {
|
|
|
53
53
|
} catch (e) { return false; }
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
try {
|
|
57
|
+
api = await (0, zalo_helper_1.getZaloApiClient)(this, {needsImageMetadataGetter: needsImageMetadataGetter});
|
|
58
|
+
if (!api) {
|
|
59
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), {}, 'Failed to initialize Zalo API. Check credentials or User ID.');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Zalo login error: ${error.message}`);
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
for (let i = 0; i < items.length; i++) {
|
|
62
67
|
try {
|
|
63
68
|
const resource = this.getNodeParameter('resource', i);
|
|
64
69
|
const operation = this.getNodeParameter('operation', i);
|
|
65
|
-
this.logger.info(`[GROUP] ${operation} - Image ${needsImageMetadataGetter}`)
|
|
70
|
+
// this.logger.info(`[GROUP] ${operation} - Image ${needsImageMetadataGetter}`)
|
|
66
71
|
|
|
67
72
|
if (resource === 'zaloGroup') {
|
|
68
73
|
switch (operation) {
|
|
@@ -43,6 +43,8 @@ const path = __importStar(require("path"));
|
|
|
43
43
|
const zalo_helper_1 = require("../utils/zalo.helper");
|
|
44
44
|
const crypto_helper_1 = require("../utils/crypto.helper");
|
|
45
45
|
const axios_1 = __importDefault(require("axios"));
|
|
46
|
+
const https_proxy_agent_1 = require("https-proxy-agent");
|
|
47
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
46
48
|
|
|
47
49
|
class ZaloLoginByQr {
|
|
48
50
|
constructor() {
|
|
@@ -73,7 +75,7 @@ class ZaloLoginByQr {
|
|
|
73
75
|
type: 'string',
|
|
74
76
|
default: '',
|
|
75
77
|
placeholder: 'https://user:pass@host:port',
|
|
76
|
-
description: 'HTTP proxy to use for Zalo API requests',
|
|
78
|
+
description: 'HTTP proxy to use for Zalo API requests (proxy can only be saved once for a custom credential)',
|
|
77
79
|
},
|
|
78
80
|
// {
|
|
79
81
|
// displayName: 'Delete Zalo Credential Duplicate UserId',
|
|
@@ -116,12 +118,28 @@ class ZaloLoginByQr {
|
|
|
116
118
|
}
|
|
117
119
|
async execute() {
|
|
118
120
|
const returnData = [];
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
let proxy = this.getNodeParameter('proxy', 0, '');
|
|
122
|
+
if (proxy) {
|
|
123
|
+
if (!proxy.toLowerCase().startsWith('http')) {
|
|
124
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Proxy không hợp lệ: "${proxy}" (http(s)://user:pass@host:port)`);
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const response = await axios_1.default.get('https://api.ipify.org', {
|
|
128
|
+
httpsAgent: new https_proxy_agent_1.HttpsProxyAgent(proxy),
|
|
129
|
+
timeout: 5000,
|
|
130
|
+
});
|
|
131
|
+
this.logger.info(`Use Proxy IP Public: ${response.data}`);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
this.logger.error(`Kiểm tra proxy thất bại: ${error.message}`);
|
|
134
|
+
let errorMessage = error.message;
|
|
135
|
+
if (error.response) {
|
|
136
|
+
errorMessage += ` - ${JSON.stringify(error.response.data)}`;
|
|
137
|
+
}
|
|
138
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Proxy không hợp lệ hoặc không thể kết nối. Lỗi: ${errorMessage} (http(s)://user:pass@host:port)`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const timeout = 45; // telegram
|
|
125
143
|
const fileName = 'zalo-qr-code.png';
|
|
126
144
|
const deleteOldZaloApi = this.getNodeParameter('deleteOldZaloApi', 0, false); // bỏ vì xoá xong gây ra lỗi k lưu được
|
|
127
145
|
const sendToTelegram = this.getNodeParameter('sendToTelegram', 0, false);
|
|
@@ -133,41 +151,18 @@ class ZaloLoginByQr {
|
|
|
133
151
|
chatId: telegramChatId,
|
|
134
152
|
logger: this.logger,
|
|
135
153
|
};
|
|
136
|
-
let
|
|
137
|
-
try {
|
|
138
|
-
n8nCredential = await this.getCredentials('n8nZaloApi');
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
}
|
|
142
|
-
const selectedCredential = n8nCredential;
|
|
143
|
-
if (selectedCredential) {
|
|
144
|
-
this.logger.info('Using n8n account credential');
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
this.logger.info('No credentials provided, will generate QR code for login');
|
|
148
|
-
}
|
|
154
|
+
let selectedCredential = await this.getCredentials('n8nZaloApi');
|
|
149
155
|
try {
|
|
150
156
|
const zaloOptions = {
|
|
151
157
|
selfListen: true,
|
|
152
|
-
logging:
|
|
158
|
+
logging: false,
|
|
153
159
|
};
|
|
154
160
|
if (proxy) {
|
|
155
|
-
zaloOptions.
|
|
156
|
-
|
|
157
|
-
let zalo;
|
|
158
|
-
if (selectedCredential) {
|
|
159
|
-
this.logger.info('Using existing Zalo credentials');
|
|
160
|
-
zalo = new zca_js_1.Zalo(zaloOptions);
|
|
161
|
-
this.logger.info('Using n8n credential to get Zalo credentials');
|
|
162
|
-
const n8nApiKey = selectedCredential.apiKey;
|
|
163
|
-
const n8nUrl = selectedCredential.url || 'http://localhost:5678';
|
|
164
|
-
this.logger.info(`Using n8n API at ${n8nUrl} with API key ${n8nApiKey ? 'provided' : 'not provided'}`);
|
|
165
|
-
this.logger.info('n8n credential support is not fully implemented yet. Will use QR code login.');
|
|
166
|
-
zalo = new zca_js_1.Zalo(zaloOptions);
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
zalo = new zca_js_1.Zalo(zaloOptions);
|
|
161
|
+
zaloOptions.agent = new https_proxy_agent_1.HttpsProxyAgent(proxy);
|
|
162
|
+
zaloOptions.polyfill = node_fetch_1.default;
|
|
170
163
|
}
|
|
164
|
+
let zalo = new zca_js_1.Zalo(zaloOptions);
|
|
165
|
+
|
|
171
166
|
this.logger.info('Starting Zalo QR login process...');
|
|
172
167
|
let userDisplayName = '';
|
|
173
168
|
let userAvatar = '';
|
|
@@ -187,12 +182,6 @@ class ZaloLoginByQr {
|
|
|
187
182
|
userImei = imei;
|
|
188
183
|
userUserAgent = userAgent;
|
|
189
184
|
userZaloUserId = zaloUserId;
|
|
190
|
-
// this.logger.info('=== ZALO CREDENTIALS ===');
|
|
191
|
-
// this.logger.info(`Cookie: ${cookie ? `Received (length: ${typeof cookie === 'string' ? cookie.length : (Array.isArray(cookie) ? cookie.length : 'unknown')})` : 'None'}`);
|
|
192
|
-
// this.logger.info(`IMEI: ${imei ? imei : 'None'}`);
|
|
193
|
-
// this.logger.info(`User Agent: ${userAgent ? userAgent : 'None'}`);
|
|
194
|
-
// this.logger.info(`Zalo User ID: ${zaloUserId ? zaloUserId : 'None'}`);
|
|
195
|
-
// this.logger.info('=== END CREDENTIALS ===');
|
|
196
185
|
};
|
|
197
186
|
const setupEventListeners = (api) => {
|
|
198
187
|
this.logger.info('Setting up event listeners to get credentials');
|
|
@@ -271,7 +260,7 @@ class ZaloLoginByQr {
|
|
|
271
260
|
}).catch(e => this.logger.error(`Không thể gửi thông báo timeout đến Telegram: ${e.message}`));
|
|
272
261
|
}
|
|
273
262
|
this.logger.warn('QR code expired. Please try again.');
|
|
274
|
-
break;
|
|
263
|
+
break;
|
|
275
264
|
case 2:
|
|
276
265
|
this.logger.info('=== QR CODE SCANNED ===');
|
|
277
266
|
if (qrEvent === null || qrEvent === void 0 ? void 0 : qrEvent.data) {
|
|
@@ -309,7 +298,6 @@ class ZaloLoginByQr {
|
|
|
309
298
|
}).catch(e => this.logger.error(`Không thể gửi thông báo đăng nhập thành công đến Telegram: ${e.message}`));
|
|
310
299
|
}
|
|
311
300
|
if (cookie && cookie.length > 0 && imei && userAgent) {
|
|
312
|
-
// Use an async IIFE to handle the async login and subsequent actions
|
|
313
301
|
(async () => {
|
|
314
302
|
try {
|
|
315
303
|
this.logger.info('Login trực tiếp để lấy UID...');
|
|
@@ -353,10 +341,9 @@ class ZaloLoginByQr {
|
|
|
353
341
|
const ports = [5678];
|
|
354
342
|
const createCredentialOnPort = async (port) => {
|
|
355
343
|
const n8nApi = await this.getCredentials('n8nZaloApi');
|
|
356
|
-
const n8nApiUrl = n8nApi.url;
|
|
357
|
-
const fullApiUrl = `${n8nApiUrl}/api/v1/credentials`;
|
|
358
344
|
const n8nApiKey = n8nApi.apiKey;
|
|
359
|
-
|
|
345
|
+
const n8nApiUrl = n8nApi.url.replace(/\/$/, '');
|
|
346
|
+
const fullApiUrl = `${n8nApiUrl}/api/v1/credentials`;
|
|
360
347
|
try {
|
|
361
348
|
const response = await axios_1.default.post(fullApiUrl, credentialApiData, {
|
|
362
349
|
headers: {
|
|
@@ -364,7 +351,7 @@ class ZaloLoginByQr {
|
|
|
364
351
|
'X-N8N-API-KEY': n8nApiKey
|
|
365
352
|
},
|
|
366
353
|
});
|
|
367
|
-
|
|
354
|
+
|
|
368
355
|
if (response.data && response.data.id) {
|
|
369
356
|
this.logger.info(`Credential ID: ${response.data.id}`);
|
|
370
357
|
createdCredentialId = response.data.id;
|
|
@@ -372,7 +359,7 @@ class ZaloLoginByQr {
|
|
|
372
359
|
try {
|
|
373
360
|
if (localUserZaloUserId && localUserZaloUserId !== 'unknown') {
|
|
374
361
|
const encryptionKey = localUserZaloUserId.repeat(3);
|
|
375
|
-
const sessionDataToEncrypt = { cookie, imei, userAgent };
|
|
362
|
+
const sessionDataToEncrypt = { cookie, imei, userAgent, proxy };
|
|
376
363
|
const encryptedData = (0, crypto_helper_1.encrypt)(sessionDataToEncrypt, encryptionKey);
|
|
377
364
|
const sessionDetails = {
|
|
378
365
|
userId: localUserZaloUserId,
|
|
@@ -505,9 +492,7 @@ class ZaloLoginByQr {
|
|
|
505
492
|
json: {
|
|
506
493
|
success: true,
|
|
507
494
|
state: '',
|
|
508
|
-
message: selectedCredential
|
|
509
|
-
? 'Using n8n account credential. QR code generated successfully.'
|
|
510
|
-
: 'QR code generated successfully. Scan with Zalo app to login.',
|
|
495
|
+
message: selectedCredential ? 'Using n8n account credential. QR code generated successfully.' : 'Missing n8n api',
|
|
511
496
|
fileName,
|
|
512
497
|
usingExistingCredential: !!selectedCredential,
|
|
513
498
|
credentialType: selectedCredential ? 'n8nZaloApi' : null,
|
|
@@ -561,10 +561,8 @@ class ZaloSendMessage {
|
|
|
561
561
|
const returnData = [];
|
|
562
562
|
const items = this.getInputData();
|
|
563
563
|
try {
|
|
564
|
-
// Use the new helper function to get the API client
|
|
565
564
|
api = await (0, zalo_helper_1.getZaloApiClient)(this, { needsImageMetadataGetter: true });
|
|
566
565
|
if (!api) {
|
|
567
|
-
// The helper function will throw an error, but as a fallback:
|
|
568
566
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to initialize Zalo API. Check credentials or User ID.');
|
|
569
567
|
}
|
|
570
568
|
}
|
|
@@ -577,7 +575,6 @@ class ZaloSendMessage {
|
|
|
577
575
|
},
|
|
578
576
|
});
|
|
579
577
|
return [returnData]
|
|
580
|
-
// throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Zalo login error: ${error.message}`);
|
|
581
578
|
}
|
|
582
579
|
for (let i = 0; i < items.length; i++) {
|
|
583
580
|
try {
|
|
@@ -595,7 +592,6 @@ class ZaloSendMessage {
|
|
|
595
592
|
const attachmentUrls = this.getNodeParameter('attachmentUrls', i, '');
|
|
596
593
|
const reactionParam = this.getNodeParameter('reaction', i, {});
|
|
597
594
|
|
|
598
|
-
// return json
|
|
599
595
|
const returnJson = {
|
|
600
596
|
threadId,
|
|
601
597
|
threadType: type
|
|
@@ -677,15 +673,12 @@ class ZaloSendMessage {
|
|
|
677
673
|
messageContent.attachments = [];
|
|
678
674
|
for (const url of urls) {
|
|
679
675
|
try {
|
|
680
|
-
// Validate URL format
|
|
681
676
|
new URL(url);
|
|
682
|
-
// Download file and get its local path
|
|
683
677
|
const fileData = await (0, helper_1.saveFile)(url);
|
|
684
678
|
messageContent.attachments.push(fileData);
|
|
685
679
|
this.logger.info(`Successfully prepared attachment from URL: ${url}`);
|
|
686
680
|
}
|
|
687
681
|
catch (e) {
|
|
688
|
-
// Log a warning and skip if the URL is invalid or the file can't be downloaded
|
|
689
682
|
this.logger.warn(`Skipping invalid or inaccessible attachment URL: ${url}. Error: ${e.message}`);
|
|
690
683
|
}
|
|
691
684
|
}
|
|
@@ -200,8 +200,7 @@ class ZaloTrigger {
|
|
|
200
200
|
continueOnFail: continueOnFail,
|
|
201
201
|
};
|
|
202
202
|
|
|
203
|
-
let telegramToken;
|
|
204
|
-
let telegramChatId;
|
|
203
|
+
let telegramToken, telegramChatId, telegramOptions;
|
|
205
204
|
if (continueOnFail) {
|
|
206
205
|
const tokenOverride = this.getNodeParameter('telegramTokenOverride', 0, '');
|
|
207
206
|
const chatIdOverride = this.getNodeParameter('telegramChatIdOverride', 0, '');
|
|
@@ -211,12 +210,25 @@ class ZaloTrigger {
|
|
|
211
210
|
telegramChatId = chatIdOverride || credsChatId;
|
|
212
211
|
if (!telegramToken || !telegramChatId) {
|
|
213
212
|
context.errorNotification = 'Vui lòng cung cấp BotToken và Chat_id để nhận thông báo khi phát hiện lỗi';
|
|
213
|
+
} else {
|
|
214
|
+
telegramOptions = {
|
|
215
|
+
token: telegramToken,
|
|
216
|
+
chatId: telegramChatId,
|
|
217
|
+
logger: this.logger,
|
|
218
|
+
};
|
|
214
219
|
}
|
|
215
220
|
}
|
|
221
|
+
const sendTelegramNotification = (message) => {
|
|
222
|
+
if (telegramOptions) {
|
|
223
|
+
(0, zalo_helper_1.sendToTelegram)({
|
|
224
|
+
...telegramOptions,
|
|
225
|
+
text: message,
|
|
226
|
+
}).catch(e => this.logger.error(`Failed to send Telegram notification: ${e.message}`));
|
|
227
|
+
}
|
|
228
|
+
};
|
|
216
229
|
|
|
217
230
|
let api = apiInstances.get(instanceKey);
|
|
218
231
|
let closeFunction = async () => { };
|
|
219
|
-
|
|
220
232
|
try {
|
|
221
233
|
if (apiInstances.has(instanceKey)) {
|
|
222
234
|
this.logger.info(`Instance ${instanceKey} already exists, reusing...`);
|
|
@@ -226,7 +238,6 @@ class ZaloTrigger {
|
|
|
226
238
|
if (!api) {
|
|
227
239
|
throw new Error('No API instance found. Please make sure to provide valid credentials.');
|
|
228
240
|
}
|
|
229
|
-
|
|
230
241
|
try {
|
|
231
242
|
const fetchedInfo = await api.fetchAccountInfo();
|
|
232
243
|
if (fetchedInfo && fetchedInfo.profile) {
|
|
@@ -261,7 +272,6 @@ class ZaloTrigger {
|
|
|
261
272
|
catch (err) {
|
|
262
273
|
this.logger.error(`[Zalo ${mode}] Failed to fetch account info: ${err.message}`);
|
|
263
274
|
}
|
|
264
|
-
|
|
265
275
|
apiInstances.set(instanceKey, api);
|
|
266
276
|
}
|
|
267
277
|
|
|
@@ -270,7 +280,6 @@ class ZaloTrigger {
|
|
|
270
280
|
if (!data?.isSelf) {
|
|
271
281
|
context.logger.info(`[${apiInstances.size}] [Zalo ${context.mode} received] ${context.credentialPhone}: ${eventType}`);
|
|
272
282
|
}
|
|
273
|
-
|
|
274
283
|
const dataWithContext = {
|
|
275
284
|
...data,
|
|
276
285
|
_timestamp: new Date().toISOString(),
|
|
@@ -283,7 +292,6 @@ class ZaloTrigger {
|
|
|
283
292
|
_eventType: eventType,
|
|
284
293
|
_source: 'zalo_trigger',
|
|
285
294
|
};
|
|
286
|
-
|
|
287
295
|
const webhookData = this.getWorkflowStaticData('node');
|
|
288
296
|
if (!webhookData[context.instanceKey]) {
|
|
289
297
|
webhookData[context.instanceKey] = {};
|
|
@@ -299,7 +307,6 @@ class ZaloTrigger {
|
|
|
299
307
|
|
|
300
308
|
emitEvent(dataWithContext);
|
|
301
309
|
};
|
|
302
|
-
|
|
303
310
|
if (context.eventTypes.includes('message_user') || context.eventTypes.includes('message_group')) {
|
|
304
311
|
api.listener.on('message', async (message) => {
|
|
305
312
|
const eventType = message.type === threadTypeUser ? 'message_user' : 'message_group';
|
|
@@ -349,35 +356,30 @@ class ZaloTrigger {
|
|
|
349
356
|
await handleEvent(message, eventType);
|
|
350
357
|
});
|
|
351
358
|
}
|
|
352
|
-
|
|
353
359
|
if (context.eventTypes.includes('reaction')) {
|
|
354
360
|
api.listener.on('reaction', async (reaction) => {
|
|
355
361
|
if (context.ignoreSelfEvents && reaction.isSelf) return;
|
|
356
362
|
await handleEvent(reaction, 'reaction');
|
|
357
363
|
});
|
|
358
364
|
}
|
|
359
|
-
|
|
360
365
|
if (context.eventTypes.includes('undo')) {
|
|
361
366
|
api.listener.on('undo', async (undo) => {
|
|
362
367
|
if (context.ignoreSelfEvents && undo.isSelf) return;
|
|
363
368
|
await handleEvent(undo, 'undo');
|
|
364
369
|
});
|
|
365
370
|
}
|
|
366
|
-
|
|
367
371
|
if (context.eventTypes.includes('group_event')) {
|
|
368
372
|
api.listener.on('group_event', async (groupEvent) => {
|
|
369
373
|
if (context.ignoreSelfEvents && groupEvent.isSelf) return;
|
|
370
374
|
await handleEvent(groupEvent, 'group_event');
|
|
371
375
|
});
|
|
372
376
|
}
|
|
373
|
-
|
|
374
377
|
if (context.eventTypes.includes('friend_request')) {
|
|
375
378
|
api.listener.on('friend_event', async (friendEvent) => {
|
|
376
379
|
if (context.ignoreSelfEvents && friendEvent.isSelf) return;
|
|
377
380
|
await handleEvent(friendEvent, 'friend_request');
|
|
378
381
|
});
|
|
379
382
|
}
|
|
380
|
-
|
|
381
383
|
if (context.eventTypes.includes('seen_message')) {
|
|
382
384
|
api.listener.on('seen_messages', async (seenMessages) => {
|
|
383
385
|
for (const seen of seenMessages) {
|
|
@@ -387,7 +389,6 @@ class ZaloTrigger {
|
|
|
387
389
|
}
|
|
388
390
|
});
|
|
389
391
|
}
|
|
390
|
-
|
|
391
392
|
if (context.eventTypes.includes('typing')) {
|
|
392
393
|
api.listener.on('typing', async (typing) => {
|
|
393
394
|
if (seen.type === threadTypeUser && !seen.isSelf) {
|
|
@@ -396,7 +397,22 @@ class ZaloTrigger {
|
|
|
396
397
|
});
|
|
397
398
|
}
|
|
398
399
|
|
|
399
|
-
|
|
400
|
+
api.listener.on('disconnected', (code, reason) => {
|
|
401
|
+
context.logger.warn(`[Zalo ${context.mode}] disconnected: ${currentPhone} - ${code}: "${reason || 'No reason'}". Attempting to reconnect...`);
|
|
402
|
+
if (reason !== 'NORMAL_CLOSURE') {
|
|
403
|
+
sendTelegramNotification(`[Zalo ${context.mode}] disconnected: ${currentPhone}: err: "${reason || 'No reason'}". Attempting to reconnect...`);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
api.listener.on('closed', (code, reason) => {
|
|
407
|
+
context.logger.warn(`[Zalo ${context.mode}] closed: ${currentPhone} - ${code}: "${reason || 'No reason'}" (stop trigger)`);
|
|
408
|
+
if (reason !== 'NORMAL_CLOSURE') {
|
|
409
|
+
sendTelegramNotification(`[Zalo ${context.mode}] closed: ${currentPhone}: err: "${reason || 'No reason'}" (stop trigger)`);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
api.listener.on('error', (error) => {
|
|
413
|
+
const errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
414
|
+
context.logger.warn(`[Zalo ${context.mode}] WebSocket Error: ${currentPhone} - ${errorMessage}`);
|
|
415
|
+
});
|
|
400
416
|
api.listener.start({ retryOnClose: true });
|
|
401
417
|
this.logger.info(`[${apiInstances.size}] [Zalo ${mode}] Listening: ${currentPhone} - ${currentName} (${context.eventTypes})`);
|
|
402
418
|
|
|
@@ -497,14 +513,8 @@ class ZaloTrigger {
|
|
|
497
513
|
catch (error) {
|
|
498
514
|
const continueOnFail = this.getNodeParameter('continueOnFail', 0, false);
|
|
499
515
|
if (continueOnFail && mode !== 'manual') {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
(0, zalo_helper_1.sendToTelegram)({
|
|
503
|
-
token: telegramToken,
|
|
504
|
-
chatId: telegramChatId,
|
|
505
|
-
text: errorMessage,
|
|
506
|
-
}).catch(e => this.logger.error(`Failed to send Telegram notification: ${e.message}`));
|
|
507
|
-
}
|
|
516
|
+
const errorMessage = `⚠️ **Lỗi Kích Hoạt Zalo Trigger**\n\n- Tài khoản: ${currentName}\n- SĐT: ${currentPhone}\n- Lý do: ${error.message} (bỏ qua node này tiếp tục active workflow)`;
|
|
517
|
+
sendTelegramNotification(errorMessage);
|
|
508
518
|
return {
|
|
509
519
|
closeFunction: async () => { },
|
|
510
520
|
manualTriggerFunction: async () => true,
|
|
@@ -47,9 +47,14 @@ class ZaloUser {
|
|
|
47
47
|
const items = this.getInputData();
|
|
48
48
|
const returnData = [];
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
try {
|
|
51
|
+
api = await (0, zalo_helper_1.getZaloApiClient)(this, {});
|
|
52
|
+
if (!api) {
|
|
53
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to initialize Zalo API. Check credentials or User ID.');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Zalo login error: ${error.message}`);
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
for (let i = 0; i < items.length; i++) {
|
|
@@ -119,7 +124,7 @@ class ZaloUser {
|
|
|
119
124
|
returnData.push({ json: { success: true, response }, pairedItem: { item: i } });
|
|
120
125
|
break;
|
|
121
126
|
}
|
|
122
|
-
case 'getFriendOnlines': {
|
|
127
|
+
case 'getFriendOnlines': { // err
|
|
123
128
|
const response = await api.getFriendOnlines();
|
|
124
129
|
returnData.push({ json: { success: true, response }, pairedItem: { item: i } });
|
|
125
130
|
break;
|
|
@@ -8,6 +8,8 @@ const fs_1 = require("fs");
|
|
|
8
8
|
const axios_1 = require("axios");
|
|
9
9
|
const FormData = require("form-data");
|
|
10
10
|
const sql_js_1 = require("sql.js");
|
|
11
|
+
const https_proxy_agent_1 = require("https-proxy-agent");
|
|
12
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
11
13
|
const crypto_helper_1 = require("./crypto.helper");
|
|
12
14
|
const path_1 = require("path");
|
|
13
15
|
|
|
@@ -86,8 +88,8 @@ exports.imageMetadataGetter = imageMetadataGetter;
|
|
|
86
88
|
async function getZaloApiClient(node, options = {}) {
|
|
87
89
|
const useSession = node.getNodeParameter('useSession', 0, false);
|
|
88
90
|
const userId = node.getNodeParameter('connectToId', 0, '');
|
|
89
|
-
const { needsImageMetadataGetter = false, selfListen = false } = options;
|
|
90
|
-
let cookie, imei, userAgent, sessionInfo = null, actualZaloId = '';
|
|
91
|
+
const { needsImageMetadataGetter = false, selfListen = false, logging = false } = options;
|
|
92
|
+
let cookie, imei, userAgent, sessionInfo, proxy = null, actualZaloId = '';
|
|
91
93
|
if (useSession && userId) {
|
|
92
94
|
try {
|
|
93
95
|
await initDb();
|
|
@@ -125,6 +127,7 @@ async function getZaloApiClient(node, options = {}) {
|
|
|
125
127
|
cookie = sessionData.cookie;
|
|
126
128
|
imei = sessionData.imei;
|
|
127
129
|
userAgent = sessionData.userAgent;
|
|
130
|
+
proxy = sessionData.proxy;
|
|
128
131
|
}
|
|
129
132
|
catch (e) {
|
|
130
133
|
throw new n8n_workflow_1.NodeOperationError(node.getNode(), `[SS] Failed to decrypt session for Zalo ID: "${actualZaloId}". The file might be corrupt or the key has changed. Error: ${e.message}`);
|
|
@@ -144,14 +147,33 @@ async function getZaloApiClient(node, options = {}) {
|
|
|
144
147
|
cookie = JSON.parse(zaloCred.cookie);
|
|
145
148
|
imei = zaloCred.imei;
|
|
146
149
|
userAgent = zaloCred.userAgent;
|
|
150
|
+
proxy = zaloCred.proxy;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const zaloOptions = {
|
|
154
|
+
logging,
|
|
155
|
+
selfListen,
|
|
156
|
+
settings: {
|
|
157
|
+
features: {
|
|
158
|
+
socket: {
|
|
159
|
+
close_and_retry_codes: [1006, 1000, 3000, 3003],
|
|
160
|
+
retries: {
|
|
161
|
+
"1006": {
|
|
162
|
+
max: 10,
|
|
163
|
+
times: [5000, 10000, 30000],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
if (proxy) {
|
|
171
|
+
zaloOptions.agent = new https_proxy_agent_1.HttpsProxyAgent(proxy);
|
|
172
|
+
zaloOptions.polyfill = node_fetch_1.default;
|
|
147
173
|
}
|
|
148
|
-
const zaloOptions = {};
|
|
149
174
|
if (needsImageMetadataGetter) {
|
|
150
175
|
zaloOptions.imageMetadataGetter = imageMetadataGetter;
|
|
151
176
|
}
|
|
152
|
-
if (selfListen) {
|
|
153
|
-
zaloOptions.selfListen = selfListen;
|
|
154
|
-
}
|
|
155
177
|
const zalo = new zca_js_1.Zalo(zaloOptions);
|
|
156
178
|
return zalo.login({ cookie, imei, userAgent });
|
|
157
179
|
}
|
|
@@ -242,7 +264,7 @@ exports.deleteSessionByUserId = deleteSessionByUserId;
|
|
|
242
264
|
|
|
243
265
|
async function sendToTelegram({ token, chatId, text, binaryData, fileName, caption, logger, }) {
|
|
244
266
|
if (!token || !chatId) {
|
|
245
|
-
logger === null || logger === void 0 ? void 0 : logger.warn('Telegram token và chatId là bắt buộc
|
|
267
|
+
logger === null || logger === void 0 ? void 0 : logger.warn('Telegram token và chatId là bắt buộc');
|
|
246
268
|
return;
|
|
247
269
|
}
|
|
248
270
|
const baseUrl = `https://api.telegram.org/bot${token}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-zalo-custom",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "n8n nodes for Zalo automation. Send messages, manage groups, friends, and listen to events without third-party services.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n",
|
|
@@ -51,6 +51,8 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"axios": "^1.8.4",
|
|
53
53
|
"express": "^5.1.0",
|
|
54
|
+
"https-proxy-agent": "^7.0.5",
|
|
55
|
+
"node-fetch": "^3.3.2",
|
|
54
56
|
"zca-js": "^2.0.4",
|
|
55
57
|
"image-size": "^1.1.1",
|
|
56
58
|
"sql.js": "^1.10.3"
|