n8n-nodes-zalo-custom 1.0.8 → 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 +14 -9
- package/nodes/ZaloCommunication/ZaloCommunication.node.js +6 -6
- package/nodes/ZaloGroup/ZaloGroup.node.js +30 -4
- package/nodes/ZaloGroup/ZaloGroupDescription.js +69 -1
- package/nodes/ZaloLoginByQr/ZaloLoginByQr.node.js +37 -52
- package/nodes/ZaloSendMessage/ZaloSendMessage.node.js +0 -7
- package/nodes/ZaloTrigger/ZaloTrigger.node.js +79 -45
- package/nodes/ZaloUser/ZaloUser.node.js +83 -3
- package/nodes/ZaloUser/ZaloUserDescription.js +35 -2
- package/nodes/utils/zalo.helper.js +36 -62
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -19,24 +19,27 @@ Vui lòng liên hệ để yêu cầu tính năng mới hoặc báo lỗi.
|
|
|
19
19
|
|
|
20
20
|
| 💯 | Tính Năng |
|
|
21
21
|
|:----:|:--------------------------------------------------|
|
|
22
|
-
| 🔑 | **_XÁC THỰC & KẾT NỐI_**
|
|
22
|
+
| 🔑 | **_XÁC THỰC & KẾT NỐI_** |
|
|
23
23
|
| ✓ | Đăng nhập bằng Mã QR |
|
|
24
24
|
| ✓✓ | Expression Zalo Credential |
|
|
25
25
|
| ✓ | Hỗ trợ đăng nhập nhiều tài khoản, proxy |
|
|
26
26
|
| ✓✓ | Đăng nhập, thông báo qua telegram |
|
|
27
|
-
| ⚡️ | **_TRIGGER_**
|
|
27
|
+
| ⚡️ | **_TRIGGER_** |
|
|
28
28
|
| ✓ | Sự kiện tin nhắn mới (lọc theo từ khóa, nhóm) |
|
|
29
29
|
| ✓ | Sự kiện thu hồi tin nhắn, thả cảm xúc |
|
|
30
30
|
| ✓ | Sự kiện trong nhóm (tham gia, rời, đổi quyền,...) |
|
|
31
|
-
|
|
|
32
|
-
|
|
|
31
|
+
| ✓✓ | Sự kiện về bạn bè (kết bạn, hủy bạn) |
|
|
32
|
+
| ✓✓ | Sự kiện bạn bè đã xem tin nhắn |
|
|
33
|
+
| ✓✓ | Sự kiện bạn bè đang soạn tin |
|
|
34
|
+
| 💬 | **_NHẮN TIN_** |
|
|
33
35
|
| ✓ | Gửi tin nhắn (Văn bản, Ảnh, Sticker) |
|
|
34
36
|
| ✓ | Trả lời tin nhắn (Quote) |
|
|
35
37
|
| ✓ | Gắn thẻ (Tag) thành viên trong nhóm |
|
|
36
38
|
| ✓✓ | Thả/Gỡ cảm xúc (Reaction) vào tin nhắn |
|
|
37
39
|
| ✓ | Thu hồi tin nhắn đã gửi |
|
|
40
|
+
| ✓✓ | Lấy danh sách tin nhắn cũ |
|
|
38
41
|
| ✓ | Mô phỏng trạng thái "Đang soạn tin..." |
|
|
39
|
-
| 👤 | **_QUẢN LÝ TÀI KHOẢN & BẠN BÈ_**
|
|
42
|
+
| 👤 | **_QUẢN LÝ TÀI KHOẢN & BẠN BÈ_** |
|
|
40
43
|
| ✓ | Gửi / Hủy lời mời kết bạn |
|
|
41
44
|
| ✓ | Chấp nhận / Từ chối lời mời kết bạn |
|
|
42
45
|
| ✓ | Hủy kết bạn (xóa bạn) |
|
|
@@ -45,18 +48,19 @@ Vui lòng liên hệ để yêu cầu tính năng mới hoặc báo lỗi.
|
|
|
45
48
|
| ✓ | Lấy thông tin chi tiết người dùng |
|
|
46
49
|
| ✓ | Cập nhật thông tin cá nhân (Tên, Giới tính,...) |
|
|
47
50
|
| ✓ | Chặn / Bỏ chặn người dùng |
|
|
48
|
-
| 👥 | **_QUẢN LÝ NHÓM_**
|
|
51
|
+
| 👥 | **_QUẢN LÝ NHÓM_** |
|
|
49
52
|
| ✓ | Tạo nhóm mới |
|
|
50
53
|
| ✓ | Thêm / Xóa thành viên khỏi nhóm |
|
|
54
|
+
| ✓✓ | Xóa tin nhắn thành viên |
|
|
51
55
|
| ✓ | Giải tán nhóm |
|
|
52
56
|
| ✓ | Thay đổi tên & ảnh đại diện nhóm |
|
|
53
57
|
| ✓ | Bổ nhiệm quyền Phó nhóm |
|
|
54
58
|
| ✓✓ | Chuyển quyền Trưởng nhóm |
|
|
55
59
|
| ✓ | Lấy danh sách tất cả các nhóm đã tham gia |
|
|
56
|
-
| ✓ | Lấy thông tin
|
|
60
|
+
| ✓ | Lấy thông tin nhóm (từ ID hoặc link) |
|
|
57
61
|
| ✓ | Tham gia nhóm bằng link / Rời nhóm |
|
|
58
62
|
| ✓ | Tạo ghi chú (Note) trong nhóm |
|
|
59
|
-
| 🎨 | **_KHÁC_**
|
|
63
|
+
| 🎨 | **_KHÁC_** |
|
|
60
64
|
| ✓ | Tạo bình chọn (Poll) trong nhóm |
|
|
61
65
|
| ✓ | Quản lý thẻ phân loại (Tag) |
|
|
62
66
|
| ✓ | Tìm kiếm sticker |
|
|
@@ -155,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ộ
|
|
|
155
159
|
<details>
|
|
156
160
|
<summary><b>nhận sự kiện từ Zalo để phản hồi</b></summary>
|
|
157
161
|
|
|
158
|
-
- **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.
|
|
159
163
|
- **Cấu hình**:
|
|
160
164
|
- Chấp nhận hoặc loại trừ các ID nhóm khi nhận sự kiện tin nhắn.
|
|
161
165
|
- Chỉ nhận theo từ khoá hoặc loại trừ khi nhận sự kiện tin nhắn.
|
|
@@ -196,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ộ
|
|
|
196
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.
|
|
197
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.
|
|
198
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.
|
|
199
204
|
</details>
|
|
200
205
|
|
|
201
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) {
|
|
@@ -263,6 +268,12 @@ class ZaloGroup {
|
|
|
263
268
|
returnData.push({ json: { success: true, response }, pairedItem: { item: i } });
|
|
264
269
|
break;
|
|
265
270
|
}
|
|
271
|
+
case 'getGroupLinkInfo': {
|
|
272
|
+
const groupLink = this.getNodeParameter('groupLink', i);
|
|
273
|
+
const response = await api.getGroupLinkInfo({ link: groupLink });
|
|
274
|
+
returnData.push({ json: { success: true, response }, pairedItem: { item: i } });
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
266
277
|
case 'joinGroupLink': {
|
|
267
278
|
const groupLink = this.getNodeParameter('groupLink', i);
|
|
268
279
|
const response = await api.joinGroupLink(groupLink);
|
|
@@ -282,6 +293,21 @@ class ZaloGroup {
|
|
|
282
293
|
returnData.push({ json: { success: true, response }, pairedItem: { item: i } });
|
|
283
294
|
break;
|
|
284
295
|
}
|
|
296
|
+
case 'deleteMessage': {
|
|
297
|
+
const groupId = this.getNodeParameter('groupId', i);
|
|
298
|
+
const uidFrom = this.getNodeParameter('userId', i);
|
|
299
|
+
const msgId = this.getNodeParameter('msgId', i);
|
|
300
|
+
const cliMsgId = this.getNodeParameter('cliMsgId', i);
|
|
301
|
+
const onlyMe = this.getNodeParameter('onlyMe', i, true);
|
|
302
|
+
const dest = {
|
|
303
|
+
data: { cliMsgId, msgId, uidFrom },
|
|
304
|
+
threadId: groupId,
|
|
305
|
+
type: 1, // ThreadType.Group
|
|
306
|
+
};
|
|
307
|
+
const response = await api.deleteMessage(dest, onlyMe);
|
|
308
|
+
returnData.push({ json: { success: true, response }, pairedItem: { item: i } });
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
285
311
|
}
|
|
286
312
|
}
|
|
287
313
|
}
|
|
@@ -79,6 +79,12 @@ exports.zaloGroupOperations = [
|
|
|
79
79
|
description: 'Lấy Link Tham Gia Nhóm',
|
|
80
80
|
action: 'Lấy Link Mời Nhóm',
|
|
81
81
|
},
|
|
82
|
+
{
|
|
83
|
+
name: 'Lấy Thông Tin Từ Link Nhóm',
|
|
84
|
+
value: 'getGroupLinkInfo',
|
|
85
|
+
description: 'Lấy thông tin của một nhóm từ link mời',
|
|
86
|
+
action: 'Lấy Thông Tin Từ Link Nhóm',
|
|
87
|
+
},
|
|
82
88
|
{
|
|
83
89
|
name: 'Xóa Thành Viên Khỏi Nhóm',
|
|
84
90
|
value: 'removeUserFromGroup',
|
|
@@ -103,6 +109,12 @@ exports.zaloGroupOperations = [
|
|
|
103
109
|
description: 'Giải tán một nhóm. Chỉ chủ nhóm mới có thể thực hiện.',
|
|
104
110
|
action: 'Giải Tán Nhóm',
|
|
105
111
|
},
|
|
112
|
+
{
|
|
113
|
+
name: 'Xóa Tin Nhắn Của Thành Viên',
|
|
114
|
+
value: 'deleteMessage',
|
|
115
|
+
description: 'Xóa tin nhắn của thành viên trong nhóm (yêu cầu quyền admin)',
|
|
116
|
+
action: 'Xóa Tin Nhắn Của Thành Viên',
|
|
117
|
+
},
|
|
106
118
|
],
|
|
107
119
|
default: 'createGroup',
|
|
108
120
|
},
|
|
@@ -144,6 +156,7 @@ exports.zaloGroupFields = [
|
|
|
144
156
|
'changeGroupOwner',
|
|
145
157
|
'leaveGroup',
|
|
146
158
|
'disperseGroup',
|
|
159
|
+
'deleteMessage',
|
|
147
160
|
],
|
|
148
161
|
},
|
|
149
162
|
},
|
|
@@ -158,7 +171,7 @@ exports.zaloGroupFields = [
|
|
|
158
171
|
displayOptions: {
|
|
159
172
|
show: {
|
|
160
173
|
resource: ['zaloGroup'],
|
|
161
|
-
operation: ['addGroupDeputy', 'changeGroupOwner'],
|
|
174
|
+
operation: ['addGroupDeputy', 'changeGroupOwner', 'deleteMessage'],
|
|
162
175
|
},
|
|
163
176
|
},
|
|
164
177
|
description: 'ID của người dùng',
|
|
@@ -301,6 +314,47 @@ exports.zaloGroupFields = [
|
|
|
301
314
|
},
|
|
302
315
|
description: 'Link để tham gia nhóm (ví dụ: https://zalo.me/g/xxxxxx)',
|
|
303
316
|
},
|
|
317
|
+
{
|
|
318
|
+
displayName: 'Message ID',
|
|
319
|
+
name: 'msgId',
|
|
320
|
+
type: 'string',
|
|
321
|
+
required: true,
|
|
322
|
+
default: '',
|
|
323
|
+
displayOptions: {
|
|
324
|
+
show: {
|
|
325
|
+
resource: ['zaloGroup'],
|
|
326
|
+
operation: ['deleteMessage'],
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
description: 'msgId của tin nhắn cần xóa',
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
displayName: 'Client Message ID',
|
|
333
|
+
name: 'cliMsgId',
|
|
334
|
+
type: 'string',
|
|
335
|
+
required: true,
|
|
336
|
+
default: '',
|
|
337
|
+
displayOptions: {
|
|
338
|
+
show: {
|
|
339
|
+
resource: ['zaloGroup'],
|
|
340
|
+
operation: ['deleteMessage'],
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
description: 'cliMsgId của tin nhắn cần xóa',
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
displayName: 'Only Me',
|
|
347
|
+
name: 'onlyMe',
|
|
348
|
+
type: 'boolean',
|
|
349
|
+
default: true,
|
|
350
|
+
displayOptions: {
|
|
351
|
+
show: {
|
|
352
|
+
resource: ['zaloGroup'],
|
|
353
|
+
operation: ['deleteMessage'],
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
description: 'Bật để xóa tin nhắn ở phía bạn. Tắt để xóa với mọi người (quyền admin)',
|
|
357
|
+
},
|
|
304
358
|
// {
|
|
305
359
|
// displayName: 'Giới Hạn',
|
|
306
360
|
// name: 'limit',
|
|
@@ -315,4 +369,18 @@ exports.zaloGroupFields = [
|
|
|
315
369
|
// },
|
|
316
370
|
// description: 'Số lượng tối đa cần lấy',
|
|
317
371
|
// },
|
|
372
|
+
{
|
|
373
|
+
displayName: 'Link Mời Nhóm',
|
|
374
|
+
name: 'groupLink',
|
|
375
|
+
type: 'string',
|
|
376
|
+
required: true,
|
|
377
|
+
default: '',
|
|
378
|
+
displayOptions: {
|
|
379
|
+
show: {
|
|
380
|
+
resource: ['zaloGroup'],
|
|
381
|
+
operation: ['getGroupLinkInfo'],
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
description: 'Link để lấy thông tin nhóm (ví dụ: https://zalo.me/g/xxxxxx)',
|
|
385
|
+
},
|
|
318
386
|
];
|
|
@@ -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
|
}
|
|
@@ -39,6 +39,8 @@ class ZaloTrigger {
|
|
|
39
39
|
{ name: 'Undo', value: 'undo', description: 'Sự kiện thu hồi tin nhắn' },
|
|
40
40
|
{ name: 'Group Event', value: 'group_event', description: 'Sự kiện của nhóm (tham gia, rời đi, thăng cấp..)' },
|
|
41
41
|
{ name: 'Friend Event', value: 'friend_request', description: 'Sự kiện có người gửi yêu cầu kết bạn' },
|
|
42
|
+
{ name: 'Seen Message', value: 'seen_message', description: 'Sự kiện đã xem tin nhắn (Chat riêng, Không Self Listen)' },
|
|
43
|
+
{ name: 'Typing', value: 'typing', description: 'Sự kiện đang gõ phím (Chat riêng, Không Self Listen)' },
|
|
42
44
|
],
|
|
43
45
|
default: ['message_user', 'message_group'],
|
|
44
46
|
required: true,
|
|
@@ -50,7 +52,7 @@ class ZaloTrigger {
|
|
|
50
52
|
type: 'boolean',
|
|
51
53
|
default: false,
|
|
52
54
|
displayOptions: { show: { eventTypes: ['message_user', 'message_group'] } },
|
|
53
|
-
description: 'Chỉ định ID nhóm nhận sự kiện User/Group Message',
|
|
55
|
+
description: 'Chỉ định ID nhóm nhận sự kiện User/Group Message (không khả dụng với Execute test)',
|
|
54
56
|
},
|
|
55
57
|
{
|
|
56
58
|
displayName: 'Allowed Thread IDs',
|
|
@@ -59,7 +61,7 @@ class ZaloTrigger {
|
|
|
59
61
|
typeOptions: { textArea: true },
|
|
60
62
|
default: '',
|
|
61
63
|
displayOptions: { show: { threadIdFiltering: [true] } },
|
|
62
|
-
description: 'Chỉ lắng nghe tin nhắn từ các thread ID này (phân cách bằng dấu phẩy). Nếu để trống, sẽ lắng nghe tất
|
|
64
|
+
description: 'Chỉ lắng nghe tin nhắn từ các thread ID này (phân cách bằng dấu phẩy). Nếu để trống, sẽ lắng nghe tất cả',
|
|
63
65
|
placeholder: 'threadId1,threadId2,threadId3',
|
|
64
66
|
},
|
|
65
67
|
{
|
|
@@ -69,7 +71,7 @@ class ZaloTrigger {
|
|
|
69
71
|
typeOptions: { textArea: true },
|
|
70
72
|
default: '',
|
|
71
73
|
displayOptions: { show: { threadIdFiltering: [true] } },
|
|
72
|
-
description: 'Bỏ qua tin nhắn từ các thread ID này (phân cách bằng dấu phẩy)
|
|
74
|
+
description: 'Bỏ qua tin nhắn từ các thread ID này (phân cách bằng dấu phẩy)',
|
|
73
75
|
placeholder: 'threadId4,threadId5',
|
|
74
76
|
},
|
|
75
77
|
{
|
|
@@ -78,7 +80,7 @@ class ZaloTrigger {
|
|
|
78
80
|
type: 'boolean',
|
|
79
81
|
default: false,
|
|
80
82
|
displayOptions: { show: { eventTypes: ['message_user', 'message_group'] } },
|
|
81
|
-
description: 'Lọc tin nhắn theo từ khoá khi nhận sự kiện User/Group Message',
|
|
83
|
+
description: 'Lọc tin nhắn theo từ khoá khi nhận sự kiện User/Group Message (không khả dụng với Execute test)',
|
|
82
84
|
},
|
|
83
85
|
{
|
|
84
86
|
displayName: 'Listen Only Keywords',
|
|
@@ -117,7 +119,7 @@ class ZaloTrigger {
|
|
|
117
119
|
type: 'boolean',
|
|
118
120
|
default: true,
|
|
119
121
|
displayOptions: { show: { selfListen: [true] } },
|
|
120
|
-
description: 'Self Listen chỉ nhận sự kiện User/Group Message (
|
|
122
|
+
description: 'Self Listen chỉ nhận sự kiện User/Group Message (không khả dụng với Execute test)',
|
|
121
123
|
},
|
|
122
124
|
{
|
|
123
125
|
displayName: 'Continue On Login Fail',
|
|
@@ -158,9 +160,9 @@ class ZaloTrigger {
|
|
|
158
160
|
let currentUserId = credentials ? credentials.userId : 'N/A';
|
|
159
161
|
let currentName = credentials ? credentials.nameAccount : 'N/A';
|
|
160
162
|
let currentPhone = credentials ? credentials.phoneNumber : 'N/A';
|
|
161
|
-
|
|
162
163
|
const mode = this.getMode(); // 'trigger' or 'manual'
|
|
163
164
|
const selfListen = this.getNodeParameter('selfListen', 0, false);
|
|
165
|
+
const continueOnFail = this.getNodeParameter('continueOnFail', 0, false);
|
|
164
166
|
|
|
165
167
|
const productionKey = `${keyId}`;
|
|
166
168
|
const manualKey = `${keyId}-manual`;
|
|
@@ -195,22 +197,47 @@ class ZaloTrigger {
|
|
|
195
197
|
.map(k => k.trim().toLowerCase()).filter(k => k && k.length > 1),
|
|
196
198
|
logger: this.logger,
|
|
197
199
|
mode,
|
|
200
|
+
continueOnFail: continueOnFail,
|
|
198
201
|
};
|
|
199
202
|
|
|
200
|
-
|
|
201
|
-
|
|
203
|
+
let telegramToken, telegramChatId, telegramOptions;
|
|
204
|
+
if (continueOnFail) {
|
|
205
|
+
const tokenOverride = this.getNodeParameter('telegramTokenOverride', 0, '');
|
|
206
|
+
const chatIdOverride = this.getNodeParameter('telegramChatIdOverride', 0, '');
|
|
207
|
+
const credsToken = credentials ? credentials.telegramToken : '';
|
|
208
|
+
const credsChatId = credentials ? credentials.telegramChatId : '';
|
|
209
|
+
telegramToken = tokenOverride || credsToken;
|
|
210
|
+
telegramChatId = chatIdOverride || credsChatId;
|
|
211
|
+
if (!telegramToken || !telegramChatId) {
|
|
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
|
+
};
|
|
219
|
+
}
|
|
202
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
|
+
};
|
|
203
229
|
|
|
204
230
|
let api = apiInstances.get(instanceKey);
|
|
205
231
|
let closeFunction = async () => { };
|
|
206
|
-
|
|
207
232
|
try {
|
|
233
|
+
if (apiInstances.has(instanceKey)) {
|
|
234
|
+
this.logger.info(`Instance ${instanceKey} already exists, reusing...`);
|
|
235
|
+
}
|
|
208
236
|
if (!api) {
|
|
209
237
|
api = await (0, zalo_helper_1.getZaloApiClient)(this, { selfListen });
|
|
210
238
|
if (!api) {
|
|
211
239
|
throw new Error('No API instance found. Please make sure to provide valid credentials.');
|
|
212
240
|
}
|
|
213
|
-
|
|
214
241
|
try {
|
|
215
242
|
const fetchedInfo = await api.fetchAccountInfo();
|
|
216
243
|
if (fetchedInfo && fetchedInfo.profile) {
|
|
@@ -245,7 +272,6 @@ class ZaloTrigger {
|
|
|
245
272
|
catch (err) {
|
|
246
273
|
this.logger.error(`[Zalo ${mode}] Failed to fetch account info: ${err.message}`);
|
|
247
274
|
}
|
|
248
|
-
|
|
249
275
|
apiInstances.set(instanceKey, api);
|
|
250
276
|
}
|
|
251
277
|
|
|
@@ -254,18 +280,18 @@ class ZaloTrigger {
|
|
|
254
280
|
if (!data?.isSelf) {
|
|
255
281
|
context.logger.info(`[${apiInstances.size}] [Zalo ${context.mode} received] ${context.credentialPhone}: ${eventType}`);
|
|
256
282
|
}
|
|
257
|
-
|
|
258
283
|
const dataWithContext = {
|
|
259
284
|
...data,
|
|
260
285
|
_timestamp: new Date().toISOString(),
|
|
261
286
|
_instanceKey: context.instanceKey,
|
|
262
287
|
_name: context.credentialName,
|
|
263
288
|
_phone: context.credentialPhone,
|
|
264
|
-
|
|
289
|
+
_userId: context.credentialUserId,
|
|
290
|
+
_continueOnFail: context.continueOnFail,
|
|
291
|
+
_errorNotification: context.errorNotification,
|
|
265
292
|
_eventType: eventType,
|
|
266
293
|
_source: 'zalo_trigger',
|
|
267
294
|
};
|
|
268
|
-
|
|
269
295
|
const webhookData = this.getWorkflowStaticData('node');
|
|
270
296
|
if (!webhookData[context.instanceKey]) {
|
|
271
297
|
webhookData[context.instanceKey] = {};
|
|
@@ -281,11 +307,12 @@ class ZaloTrigger {
|
|
|
281
307
|
|
|
282
308
|
emitEvent(dataWithContext);
|
|
283
309
|
};
|
|
284
|
-
|
|
285
310
|
if (context.eventTypes.includes('message_user') || context.eventTypes.includes('message_group')) {
|
|
286
311
|
api.listener.on('message', async (message) => {
|
|
287
312
|
const eventType = message.type === threadTypeUser ? 'message_user' : 'message_group';
|
|
288
|
-
|
|
313
|
+
if (!context.eventTypes.includes(eventType)) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
289
316
|
if (context.threadIdFiltering) {
|
|
290
317
|
if (context.listenOnlyThreads.length > 0 && !context.listenOnlyThreads.includes(message.threadId)) {
|
|
291
318
|
context.logger.info(`[zalo listen] ${context.instanceKey} threadId: ${message.threadId} (not in listen-only list)`);
|
|
@@ -296,11 +323,9 @@ class ZaloTrigger {
|
|
|
296
323
|
return;
|
|
297
324
|
}
|
|
298
325
|
}
|
|
299
|
-
|
|
300
326
|
if (context.keywordFiltering && (context.listenOnlyKeywords.length > 0 || context.ignoreKeywords.length > 0)) {
|
|
301
327
|
let messageText = '';
|
|
302
328
|
const content = message?.data?.content;
|
|
303
|
-
|
|
304
329
|
if (typeof content === 'string') {
|
|
305
330
|
messageText = content;
|
|
306
331
|
}
|
|
@@ -318,7 +343,6 @@ class ZaloTrigger {
|
|
|
318
343
|
}
|
|
319
344
|
}
|
|
320
345
|
}
|
|
321
|
-
|
|
322
346
|
const lower = (messageText || '').toLowerCase();
|
|
323
347
|
if (context.listenOnlyKeywords.length > 0 && !context.listenOnlyKeywords.some(k => lower.includes(k))) {
|
|
324
348
|
context.logger.info(`[zalo listen] ${context.instanceKey} keyword: ${lower} in ${message.threadId} ignored (no listen-only keyword found)`);
|
|
@@ -329,46 +353,69 @@ class ZaloTrigger {
|
|
|
329
353
|
return;
|
|
330
354
|
}
|
|
331
355
|
}
|
|
332
|
-
|
|
333
|
-
if (context.eventTypes.includes(eventType)) {
|
|
334
|
-
await handleEvent(message, eventType);
|
|
335
|
-
}
|
|
356
|
+
await handleEvent(message, eventType);
|
|
336
357
|
});
|
|
337
358
|
}
|
|
338
|
-
|
|
339
359
|
if (context.eventTypes.includes('reaction')) {
|
|
340
360
|
api.listener.on('reaction', async (reaction) => {
|
|
341
361
|
if (context.ignoreSelfEvents && reaction.isSelf) return;
|
|
342
362
|
await handleEvent(reaction, 'reaction');
|
|
343
363
|
});
|
|
344
364
|
}
|
|
345
|
-
|
|
346
365
|
if (context.eventTypes.includes('undo')) {
|
|
347
366
|
api.listener.on('undo', async (undo) => {
|
|
348
367
|
if (context.ignoreSelfEvents && undo.isSelf) return;
|
|
349
368
|
await handleEvent(undo, 'undo');
|
|
350
369
|
});
|
|
351
370
|
}
|
|
352
|
-
|
|
353
371
|
if (context.eventTypes.includes('group_event')) {
|
|
354
372
|
api.listener.on('group_event', async (groupEvent) => {
|
|
355
373
|
if (context.ignoreSelfEvents && groupEvent.isSelf) return;
|
|
356
374
|
await handleEvent(groupEvent, 'group_event');
|
|
357
375
|
});
|
|
358
376
|
}
|
|
359
|
-
|
|
360
377
|
if (context.eventTypes.includes('friend_request')) {
|
|
361
378
|
api.listener.on('friend_event', async (friendEvent) => {
|
|
362
379
|
if (context.ignoreSelfEvents && friendEvent.isSelf) return;
|
|
363
380
|
await handleEvent(friendEvent, 'friend_request');
|
|
364
381
|
});
|
|
365
382
|
}
|
|
383
|
+
if (context.eventTypes.includes('seen_message')) {
|
|
384
|
+
api.listener.on('seen_messages', async (seenMessages) => {
|
|
385
|
+
for (const seen of seenMessages) {
|
|
386
|
+
if (seen.type === threadTypeUser && !seen.isSelf) {
|
|
387
|
+
await handleEvent(seen, 'seen_message');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
if (context.eventTypes.includes('typing')) {
|
|
393
|
+
api.listener.on('typing', async (typing) => {
|
|
394
|
+
if (seen.type === threadTypeUser && !seen.isSelf) {
|
|
395
|
+
await handleEvent(typing, 'typing');
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
366
399
|
|
|
367
|
-
|
|
368
|
-
|
|
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
|
+
});
|
|
416
|
+
api.listener.start({ retryOnClose: true });
|
|
369
417
|
this.logger.info(`[${apiInstances.size}] [Zalo ${mode}] Listening: ${currentPhone} - ${currentName} (${context.eventTypes})`);
|
|
370
418
|
|
|
371
|
-
// manual within 60s
|
|
372
419
|
const manualTriggerFunction = async () => {
|
|
373
420
|
if (mode !== 'manual') return true;
|
|
374
421
|
return await new Promise(async (resolve, reject) => {
|
|
@@ -466,21 +513,8 @@ class ZaloTrigger {
|
|
|
466
513
|
catch (error) {
|
|
467
514
|
const continueOnFail = this.getNodeParameter('continueOnFail', 0, false);
|
|
468
515
|
if (continueOnFail && mode !== 'manual') {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
const chatIdOverride = this.getNodeParameter('telegramChatIdOverride', 0, '');
|
|
472
|
-
const telegramToken = tokenOverride || credentials.telegramToken;
|
|
473
|
-
const telegramChatId = chatIdOverride || credentials.telegramChatId;
|
|
474
|
-
|
|
475
|
-
if (telegramToken && telegramChatId) {
|
|
476
|
-
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)`;
|
|
477
|
-
(0, zalo_helper_1.sendToTelegram)({
|
|
478
|
-
token: telegramToken,
|
|
479
|
-
chatId: telegramChatId,
|
|
480
|
-
text: errorMessage,
|
|
481
|
-
}).catch(e => this.logger.error(`Failed to send Telegram notification: ${e.message}`));
|
|
482
|
-
}
|
|
483
|
-
|
|
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);
|
|
484
518
|
return {
|
|
485
519
|
closeFunction: async () => { },
|
|
486
520
|
manualTriggerFunction: async () => true,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ZaloUser = void 0;
|
|
4
|
+
const zca_js_1 = require("zca-js");
|
|
4
5
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
6
|
const ZaloNodeProperties_1 = require("../shared/ZaloNodeProperties");
|
|
6
7
|
const ZaloUserDescription_1 = require("./ZaloUserDescription");
|
|
@@ -46,9 +47,14 @@ class ZaloUser {
|
|
|
46
47
|
const items = this.getInputData();
|
|
47
48
|
const returnData = [];
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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}`);
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
for (let i = 0; i < items.length; i++) {
|
|
@@ -118,6 +124,17 @@ class ZaloUser {
|
|
|
118
124
|
returnData.push({ json: { success: true, response }, pairedItem: { item: i } });
|
|
119
125
|
break;
|
|
120
126
|
}
|
|
127
|
+
case 'getFriendOnlines': { // err
|
|
128
|
+
const response = await api.getFriendOnlines();
|
|
129
|
+
returnData.push({ json: { success: true, response }, pairedItem: { item: i } });
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case 'getLastOnline': {
|
|
133
|
+
const userId = this.getNodeParameter('userId', i);
|
|
134
|
+
const response = await api.lastOnline(userId);
|
|
135
|
+
returnData.push({ json: { success: true, response }, pairedItem: { item: i } });
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
121
138
|
case 'findUser': {
|
|
122
139
|
const phoneNumber = this.getNodeParameter('phoneNumber', i);
|
|
123
140
|
const response = await api.findUser(phoneNumber);
|
|
@@ -185,6 +202,69 @@ class ZaloUser {
|
|
|
185
202
|
returnData.push({ json: { success: true, response }, pairedItem: { item: i } });
|
|
186
203
|
break;
|
|
187
204
|
}
|
|
205
|
+
case 'getOldMessages': {
|
|
206
|
+
const type = this.getNodeParameter('threadType', i);
|
|
207
|
+
const lastMsgId = this.getNodeParameter('lastMsgId', i, null);
|
|
208
|
+
api.listener.start({ retryOnClose: false });
|
|
209
|
+
const getMessages = () =>
|
|
210
|
+
new Promise((resolve, reject) => {
|
|
211
|
+
const timeout = setTimeout(() => {
|
|
212
|
+
api.listener.off('old_messages', onOldMessages);
|
|
213
|
+
reject(new Error('Timeout: Did not receive old messages within 30 seconds.'));
|
|
214
|
+
}, 30000);
|
|
215
|
+
|
|
216
|
+
const onOldMessages = (messages, threadType) => {
|
|
217
|
+
if (threadType === type) {
|
|
218
|
+
clearTimeout(timeout);
|
|
219
|
+
api.listener.off('old_messages', onOldMessages);
|
|
220
|
+
resolve(messages);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
api.listener.once('connected', () => {
|
|
224
|
+
api.listener.on('old_messages', onOldMessages);
|
|
225
|
+
api.listener.requestOldMessages(type, lastMsgId);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
let response;
|
|
229
|
+
let processedResponse = {};
|
|
230
|
+
try {
|
|
231
|
+
response = await getMessages();
|
|
232
|
+
const filteredMessages = response.filter((msg) => !msg.isSelf);
|
|
233
|
+
if (filteredMessages.length > 0) {
|
|
234
|
+
let oldestTs = Infinity;
|
|
235
|
+
let newestTs = -Infinity;
|
|
236
|
+
|
|
237
|
+
const formatTimestamp = (ts) => {
|
|
238
|
+
if (!ts) return null;
|
|
239
|
+
// Create a new Date object from the timestamp
|
|
240
|
+
const date = new Date(parseInt(ts, 10));
|
|
241
|
+
// Format it to an ISO-like string for the Vietnam timezone (UTC+7)
|
|
242
|
+
// This is more machine-readable than the previous format.
|
|
243
|
+
const parts = new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Ho_Chi_Minh', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).formatToParts(date);
|
|
244
|
+
const p = parts.reduce((acc, part) => ({ ...acc, [part.type]: part.value }), {});
|
|
245
|
+
return `${p.year}-${p.month}-${p.day}T${p.hour}:${p.minute}:${p.second}+07:00`;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
filteredMessages.forEach((msg) => {
|
|
249
|
+
const ts = parseInt(msg.data.ts, 10);
|
|
250
|
+
msg._timestamp = formatTimestamp(ts);
|
|
251
|
+
msg._eventType = 'old_messages';
|
|
252
|
+
msg._source = 'zaloUser';
|
|
253
|
+
if (ts < oldestTs) oldestTs = ts;
|
|
254
|
+
if (ts > newestTs) newestTs = ts;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
processedResponse.count = filteredMessages.length;
|
|
258
|
+
processedResponse.oldestMessageTime = formatTimestamp(oldestTs);
|
|
259
|
+
processedResponse.newestMessageTime = formatTimestamp(newestTs);
|
|
260
|
+
}
|
|
261
|
+
processedResponse.response = filteredMessages;
|
|
262
|
+
} finally {
|
|
263
|
+
api.listener.stop(); // Always stop the listener
|
|
264
|
+
}
|
|
265
|
+
returnData.push({ json: { success: true, ...processedResponse }, pairedItem: { item: i } });
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
188
268
|
}
|
|
189
269
|
}
|
|
190
270
|
}
|
|
@@ -19,6 +19,12 @@ exports.zaloUserOperations = [
|
|
|
19
19
|
action: 'Lấy danh sách bạn bè',
|
|
20
20
|
description: 'Lấy danh sách bạn bè',
|
|
21
21
|
},
|
|
22
|
+
// {
|
|
23
|
+
// name: 'Lấy Trạng Thái Online Của Bạn Bè',
|
|
24
|
+
// value: 'getFriendOnlines',
|
|
25
|
+
// action: 'Lấy trạng thái online của bạn bè',
|
|
26
|
+
// description: 'Lấy trạng thái online và thời gian truy cập cuối của bạn bè',
|
|
27
|
+
// },
|
|
22
28
|
{
|
|
23
29
|
name: 'Chấp Nhận Lời Mời Kết Bạn',
|
|
24
30
|
value: 'acceptFriendRequest',
|
|
@@ -121,6 +127,12 @@ exports.zaloUserOperations = [
|
|
|
121
127
|
action: 'Đánh dấu tin nhắn đã đọc',
|
|
122
128
|
description: 'Đánh dấu tin nhắn là đã đọc',
|
|
123
129
|
},
|
|
130
|
+
{
|
|
131
|
+
name: 'Lấy Trạng Thái Hoạt Động Của Người Dùng',
|
|
132
|
+
value: 'getLastOnline',
|
|
133
|
+
action: 'Lấy trạng thái hoạt động của người dùng',
|
|
134
|
+
description: 'Lấy thời gian hoạt động cuối cùng của một người dùng qua User ID',
|
|
135
|
+
},
|
|
124
136
|
{
|
|
125
137
|
name: 'Lấy Thông Tin Tài Khoản',
|
|
126
138
|
value: 'fetchAccountInfo',
|
|
@@ -133,6 +145,12 @@ exports.zaloUserOperations = [
|
|
|
133
145
|
action: 'Lấy ngữ cảnh tài khoản hiện tại',
|
|
134
146
|
description: 'Lấy ngữ cảnh (context) của phiên làm việc hiện tại',
|
|
135
147
|
},
|
|
148
|
+
{
|
|
149
|
+
name: 'Lấy Tin Nhắn Đến Cũ của tài khoản',
|
|
150
|
+
value: 'getOldMessages',
|
|
151
|
+
action: 'Lấy tin nhắn đến cũ của tài khoản',
|
|
152
|
+
description: 'Lấy các tin nhắn cũ hơn trong một cuộc trò chuyện',
|
|
153
|
+
},
|
|
136
154
|
],
|
|
137
155
|
default: 'getUserInfo',
|
|
138
156
|
},
|
|
@@ -160,7 +178,7 @@ exports.zaloUserFields = [
|
|
|
160
178
|
displayOptions: {
|
|
161
179
|
show: {
|
|
162
180
|
resource: ['zaloUser'],
|
|
163
|
-
operation: ['removeUnreadMark', 'addUnreadMark', 'undoMessage'],
|
|
181
|
+
operation: ['removeUnreadMark', 'addUnreadMark', 'undoMessage', 'getOldMessages'],
|
|
164
182
|
},
|
|
165
183
|
},
|
|
166
184
|
options: [
|
|
@@ -176,7 +194,7 @@ exports.zaloUserFields = [
|
|
|
176
194
|
},
|
|
177
195
|
],
|
|
178
196
|
default: 0,
|
|
179
|
-
description: 'Loại cuộc trò chuyện.
|
|
197
|
+
description: 'Loại cuộc trò chuyện.',
|
|
180
198
|
},
|
|
181
199
|
{
|
|
182
200
|
displayName: 'msgId',
|
|
@@ -225,12 +243,27 @@ exports.zaloUserFields = [
|
|
|
225
243
|
'getUserInfo',
|
|
226
244
|
'undoFriendRequest',
|
|
227
245
|
'getQR',
|
|
246
|
+
'getLastOnline',
|
|
228
247
|
],
|
|
229
248
|
},
|
|
230
249
|
},
|
|
231
250
|
default: '',
|
|
232
251
|
description: 'ID của người dùng',
|
|
233
252
|
},
|
|
253
|
+
{
|
|
254
|
+
displayName: 'Last Message ID',
|
|
255
|
+
name: 'lastMsgId',
|
|
256
|
+
type: 'string',
|
|
257
|
+
displayOptions: {
|
|
258
|
+
show: {
|
|
259
|
+
resource: ['zaloUser'],
|
|
260
|
+
operation: ['getOldMessages'],
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
default: '',
|
|
264
|
+
placeholder: 'Để trống để lấy tin nhắn gần nhất',
|
|
265
|
+
description: 'Lấy các tin nhắn cũ hơn ID này, để trống để lấy tin nhắn mới nhất (chức năng này có thể không hoạt động như mong muốn)',
|
|
266
|
+
},
|
|
234
267
|
{
|
|
235
268
|
displayName: 'Alias Name',
|
|
236
269
|
name: 'aliasName',
|
|
@@ -8,15 +8,13 @@ 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
|
|
|
14
16
|
let db;
|
|
15
17
|
|
|
16
|
-
/**
|
|
17
|
-
* Initializes the SQLite database using sql.js.
|
|
18
|
-
* Creates the database file if it doesn't exist and sets up the necessary tables.
|
|
19
|
-
*/
|
|
20
18
|
async function initDb() {
|
|
21
19
|
if (db)
|
|
22
20
|
return db;
|
|
@@ -50,9 +48,6 @@ async function initDb() {
|
|
|
50
48
|
return db;
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
/**
|
|
54
|
-
* Persists the database changes to the file system.
|
|
55
|
-
*/
|
|
56
51
|
async function persistDb() {
|
|
57
52
|
if (!db) return;
|
|
58
53
|
try {
|
|
@@ -90,29 +85,16 @@ async function imageMetadataGetter(filePath) {
|
|
|
90
85
|
}
|
|
91
86
|
exports.imageMetadataGetter = imageMetadataGetter;
|
|
92
87
|
|
|
93
|
-
/**
|
|
94
|
-
* Initializes and returns a Zalo API client.
|
|
95
|
-
* It prioritizes credentials in this order:
|
|
96
|
-
* 1. A user ID provided in the 'User ID' field, which is used to look up a session in the `zalo_sessions.json` file.
|
|
97
|
-
* 2. A Zalo API credential selected in the node's UI.
|
|
98
|
-
*
|
|
99
|
-
* @param {IExecuteFunctions} node The execution context of the node (`this`)
|
|
100
|
-
* @param {object} [options={}] Optional parameters.
|
|
101
|
-
* @param {boolean} [options.needsImageMetadataGetter=false] get image metadata.
|
|
102
|
-
* @param {boolean} [options.selfListen=false] listen owner events triggered.
|
|
103
|
-
* @returns {Promise<any>} A promise that resolves to an initialized Zalo API client.
|
|
104
|
-
* @throws {NodeOperationError} If no valid credentials or session can be found.
|
|
105
|
-
*/
|
|
106
88
|
async function getZaloApiClient(node, options = {}) {
|
|
107
89
|
const useSession = node.getNodeParameter('useSession', 0, false);
|
|
108
90
|
const userId = node.getNodeParameter('connectToId', 0, '');
|
|
109
|
-
const { needsImageMetadataGetter = false, selfListen = false } = options;
|
|
110
|
-
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 = '';
|
|
111
93
|
if (useSession && userId) {
|
|
112
94
|
try {
|
|
113
95
|
await initDb();
|
|
114
96
|
} catch (e) {
|
|
115
|
-
throw new n8n_workflow_1.NodeOperationError(node.getNode(), `
|
|
97
|
+
throw new n8n_workflow_1.NodeOperationError(node.getNode(), `Database initialization failed: ${e.message}. Please ensure a 'Zalo Login by QR' node has run successfully first.`);
|
|
116
98
|
}
|
|
117
99
|
|
|
118
100
|
try {
|
|
@@ -145,42 +127,58 @@ async function getZaloApiClient(node, options = {}) {
|
|
|
145
127
|
cookie = sessionData.cookie;
|
|
146
128
|
imei = sessionData.imei;
|
|
147
129
|
userAgent = sessionData.userAgent;
|
|
130
|
+
proxy = sessionData.proxy;
|
|
148
131
|
}
|
|
149
132
|
catch (e) {
|
|
150
|
-
throw new n8n_workflow_1.NodeOperationError(node.getNode(), `[
|
|
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}`);
|
|
151
134
|
}
|
|
152
135
|
}
|
|
153
136
|
else {
|
|
154
137
|
const idType = isPhoneNumber ? 'Phone number' : 'Zalo User ID';
|
|
155
|
-
throw new n8n_workflow_1.NodeOperationError(node.getNode(), `[
|
|
138
|
+
throw new n8n_workflow_1.NodeOperationError(node.getNode(), `[SS] ${idType} "${userId}" was provided, but no matching session was found.`);
|
|
156
139
|
}
|
|
157
140
|
}
|
|
158
141
|
catch (e) {
|
|
159
142
|
if (e instanceof n8n_workflow_1.NodeOperationError) throw e;
|
|
160
|
-
throw new n8n_workflow_1.NodeOperationError(node.getNode(), `[
|
|
143
|
+
throw new n8n_workflow_1.NodeOperationError(node.getNode(), `[SS] An unexpected database error occurred: ${e.message}.`);
|
|
161
144
|
}
|
|
162
145
|
} else if (!useSession) {
|
|
163
146
|
const zaloCred = await node.getCredentials('zaloApi');
|
|
164
147
|
cookie = JSON.parse(zaloCred.cookie);
|
|
165
148
|
imei = zaloCred.imei;
|
|
166
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;
|
|
167
173
|
}
|
|
168
|
-
const zaloOptions = {};
|
|
169
174
|
if (needsImageMetadataGetter) {
|
|
170
175
|
zaloOptions.imageMetadataGetter = imageMetadataGetter;
|
|
171
176
|
}
|
|
172
|
-
if (selfListen) {
|
|
173
|
-
zaloOptions.selfListen = selfListen;
|
|
174
|
-
}
|
|
175
177
|
const zalo = new zca_js_1.Zalo(zaloOptions);
|
|
176
178
|
return zalo.login({ cookie, imei, userAgent });
|
|
177
179
|
}
|
|
178
180
|
exports.getZaloApiClient = getZaloApiClient;
|
|
179
|
-
|
|
180
|
-
* Saves or updates a user's session in the database.
|
|
181
|
-
* @param {object} sessionDetails - The details of the session to save.
|
|
182
|
-
* @returns {Promise<{oldCredentialId: string | null}>} - The old credential ID if it existed.
|
|
183
|
-
*/
|
|
181
|
+
|
|
184
182
|
async function saveOrUpdateSession(sessionDetails) {
|
|
185
183
|
try {
|
|
186
184
|
const { userId, name, phone, credentialId, encryptedData } = sessionDetails;
|
|
@@ -223,13 +221,7 @@ async function saveOrUpdateSession(sessionDetails) {
|
|
|
223
221
|
}
|
|
224
222
|
}
|
|
225
223
|
exports.saveOrUpdateSession = saveOrUpdateSession;
|
|
226
|
-
|
|
227
|
-
* Updates only the name and phone number for a given user ID in the database.
|
|
228
|
-
* @param {string} userId - The Zalo user ID.
|
|
229
|
-
* @param {string} name - The new display name.
|
|
230
|
-
* @param {string} phone - The new phone number.
|
|
231
|
-
* @returns {Promise<void>}
|
|
232
|
-
*/
|
|
224
|
+
|
|
233
225
|
async function updateSessionInfo(userId, name, phone) {
|
|
234
226
|
try {
|
|
235
227
|
await initDb();
|
|
@@ -255,11 +247,7 @@ async function updateSessionInfo(userId, name, phone) {
|
|
|
255
247
|
}
|
|
256
248
|
}
|
|
257
249
|
exports.updateSessionInfo = updateSessionInfo;
|
|
258
|
-
|
|
259
|
-
* Deletes a user's session from the database by user ID.
|
|
260
|
-
* @param {string} userId - The Zalo user ID of the session to delete.
|
|
261
|
-
* @returns {Promise<void>}
|
|
262
|
-
*/
|
|
250
|
+
|
|
263
251
|
async function deleteSessionByUserId(userId) {
|
|
264
252
|
try {
|
|
265
253
|
await initDb();
|
|
@@ -274,23 +262,9 @@ async function deleteSessionByUserId(userId) {
|
|
|
274
262
|
}
|
|
275
263
|
exports.deleteSessionByUserId = deleteSessionByUserId;
|
|
276
264
|
|
|
277
|
-
/**
|
|
278
|
-
* Gửi tin nhắn hoặc file đến một cuộc trò chuyện trên Telegram.
|
|
279
|
-
*
|
|
280
|
-
* @param {object} params Các tham số để gửi đến Telegram.
|
|
281
|
-
* @param {string} params.token Token của bot Telegram.
|
|
282
|
-
* @param {string} params.chatId ID của cuộc trò chuyện trên Telegram.
|
|
283
|
-
* @param {string} [params.text] Tin nhắn văn bản cần gửi.
|
|
284
|
-
* @param {Buffer} [params.binaryData] Dữ liệu nhị phân của file cần gửi.
|
|
285
|
-
* @param {string} [params.fileName] Tên của file.
|
|
286
|
-
* @param {string} [params.caption] Chú thích cho file.
|
|
287
|
-
* @param {INodeExecutionFunctions['logger']} [params.logger] Logger để ghi lại thông tin.
|
|
288
|
-
* @returns {Promise<void>}
|
|
289
|
-
* @throws {Error} Nếu gửi thất bại.
|
|
290
|
-
*/
|
|
291
265
|
async function sendToTelegram({ token, chatId, text, binaryData, fileName, caption, logger, }) {
|
|
292
266
|
if (!token || !chatId) {
|
|
293
|
-
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');
|
|
294
268
|
return;
|
|
295
269
|
}
|
|
296
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"
|