n8n-nodes-zalo-custom 1.0.9 → 1.1.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.
@@ -0,0 +1,389 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ZaloSendMessageDescription = exports.reactionOptions = void 0;
4
+ exports.reactionOptions = [
5
+ { name: 'Heart', value: 'heart' },
6
+ { name: 'Like', value: 'like' },
7
+ { name: 'Haha', value: 'haha' },
8
+ { name: 'Wow', value: 'wow' },
9
+ { name: 'Cry', value: 'cry' },
10
+ { name: 'Angry', value: 'angry' },
11
+ { name: 'Kiss', value: 'kiss' },
12
+ { name: 'Tears of Joy', value: 'tears_of_joy' },
13
+ { name: 'Dislike', value: 'dislike' },
14
+ { name: 'OK', value: 'ok' },
15
+ { name: 'Thanks', value: 'thanks' },
16
+ { name: 'Big Smile', value: 'big_smile' },
17
+ { name: 'Sad', value: 'sad' },
18
+ { name: 'Very Sad', value: 'very_sad' },
19
+ { name: 'Rose', value: 'rose' },
20
+ { name: 'Broken Heart', value: 'broken_heart' },
21
+ { name: 'Love', value: 'love' },
22
+ { name: 'Confused', value: 'confused' },
23
+ { name: 'Wink', value: 'wink' },
24
+ { name: 'Birthday', value: 'birthday' },
25
+ { name: 'Bomb', value: 'bomb' },
26
+ { name: 'Peace', value: 'peace' },
27
+ { name: 'Pray', value: 'pray' },
28
+ { name: 'No', value: 'no' },
29
+ { name: 'Love You', value: 'love_you' },
30
+ { name: 'Cool', value: 'cool' },
31
+ { name: 'Nerd', value: 'nerd' },
32
+ { name: 'Sunglasses', value: 'sunglasses' },
33
+ { name: 'Neutral', value: 'neutral' },
34
+ { name: 'Bye', value: 'bye' },
35
+ { name: 'Sleepy', value: 'sleepy' },
36
+ { name: 'Wipe', value: 'wipe' },
37
+ { name: 'Dig', value: 'dig' },
38
+ { name: 'Handclap', value: 'handclap' },
39
+ { name: 'Silent', value: 'silent' },
40
+ { name: 'Surprise', value: 'surprise' },
41
+ { name: 'Embarrassed', value: 'embarrassed' },
42
+ { name: 'Afraid', value: 'afraid' },
43
+ { name: 'Big Laugh', value: 'big_laugh' },
44
+ { name: 'Beer', value: 'beer' },
45
+ ];
46
+
47
+ exports.ZaloSendMessageDescription = [
48
+ {
49
+ displayName: 'Thread ID',
50
+ name: 'threadId',
51
+ type: 'string',
52
+ default: '',
53
+ required: true,
54
+ description: 'ID của thread để gửi tin nhắn',
55
+ },
56
+ {
57
+ displayName: 'Type',
58
+ name: 'type',
59
+ type: 'options',
60
+ options: [
61
+ {
62
+ name: 'User',
63
+ value: 0,
64
+ },
65
+ {
66
+ name: 'Group',
67
+ value: 1,
68
+ },
69
+ ],
70
+ default: 0,
71
+ description: 'Tin nhắn gửi đến (người dùng hoặc nhóm)',
72
+ },
73
+ {
74
+ displayName: 'Message',
75
+ name: 'message',
76
+ type: 'string',
77
+ default: '',
78
+ required: true,
79
+ description: 'Nội dung tin nhắn cần gửi',
80
+ },
81
+ {
82
+ displayName: 'Message Format',
83
+ name: 'messageFormat',
84
+ type: 'options',
85
+ options: [
86
+ {
87
+ name: 'Plain Text',
88
+ value: 'plain',
89
+ },
90
+ {
91
+ name: 'HTML',
92
+ value: 'html',
93
+ },
94
+ ],
95
+ default: 'plain',
96
+ description: 'Định dạng mặc định hoặc HTML',
97
+ },
98
+ {
99
+ displayName: 'Urgency',
100
+ name: 'urgency',
101
+ type: 'options',
102
+ options: [
103
+ {
104
+ name: 'Default',
105
+ value: 0,
106
+ description: 'Tin nhắn mặc định',
107
+ },
108
+ {
109
+ name: 'Important',
110
+ value: 1,
111
+ description: 'Đánh dấu tin nhắn quan trọng',
112
+ },
113
+ {
114
+ name: 'Urgent',
115
+ value: 2,
116
+ description: 'Người nhận được thông báo nhiều lần trong vài phút',
117
+ },
118
+ ],
119
+ default: 0,
120
+ description: 'Mức độ khẩn cấp của tin nhắn',
121
+ },
122
+ {
123
+ displayName: 'Image URLs',
124
+ name: 'attachmentUrls',
125
+ type: 'string',
126
+ default: '',
127
+ placeholder: 'https://../img1.jpg, https://../img2.png',
128
+ description: 'Nhập 1 hoặc nhiều url ảnh cách nhau với dấu (,)',
129
+ },
130
+ {
131
+ displayName: 'Send "Typing..." Event',
132
+ name: 'sendTypingEvent',
133
+ type: 'boolean',
134
+ default: true,
135
+ description: 'Gửi sự kiện "Đang soạn tin..." (tự động chờ 1 chút trước khi gửi tin)',
136
+ },
137
+ {
138
+ displayName: 'Reaction',
139
+ name: 'reaction',
140
+ type: 'fixedCollection',
141
+ typeOptions: {
142
+ multipleValues: false,
143
+ },
144
+ placeholder: 'Add Reaction',
145
+ default: {},
146
+ options: [
147
+ {
148
+ displayName: 'Reaction Settings',
149
+ name: 'reactionValue',
150
+ values: [
151
+ {
152
+ displayName: 'Reaction Icon',
153
+ name: 'reactionIcon',
154
+ type: 'options',
155
+ options: exports.reactionOptions,
156
+ default: 'heart',
157
+ description: 'Biểu tượng cảm xúc',
158
+ },
159
+ {
160
+ displayName: 'Message ID',
161
+ name: 'reactionMsgId',
162
+ type: 'string',
163
+ default: '',
164
+ description: 'msgId của tin nhắn hiện có để reaction',
165
+ },
166
+ {
167
+ displayName: 'Client Message ID',
168
+ name: 'reactionCliMsgId',
169
+ type: 'string',
170
+ default: '',
171
+ description: 'cliMsgId của tin nhắn hiện có để reaction',
172
+ },
173
+ ],
174
+ },
175
+ ],
176
+ },
177
+ {
178
+ displayName: 'Quote Message',
179
+ name: 'quote',
180
+ type: 'fixedCollection',
181
+ typeOptions: {
182
+ multipleValues: false,
183
+ },
184
+ placeholder: 'Add Quote',
185
+ default: {},
186
+ options: [
187
+ {
188
+ displayName: 'Quote Details',
189
+ name: 'quoteValue',
190
+ values: [
191
+ {
192
+ displayName: 'User ID',
193
+ name: 'uidFrom',
194
+ type: 'string',
195
+ default: '',
196
+ description: 'ID của người gửi tin nhắn (không phải threadId của group)',
197
+ },
198
+ {
199
+ displayName: 'Message ID',
200
+ name: 'msgId',
201
+ type: 'string',
202
+ default: '',
203
+ description: 'msgId của tin nhắn cần trích dẫn',
204
+ },
205
+ {
206
+ displayName: 'Client Message ID',
207
+ name: 'cliMsgId',
208
+ type: 'string',
209
+ default: '',
210
+ description: 'cliMsgId của tin nhắn cần trích dẫn',
211
+ },
212
+ {
213
+ displayName: 'Content',
214
+ name: 'content',
215
+ type: 'string',
216
+ default: '',
217
+ description: 'Nội dung tin nhắn trích dẫn',
218
+ },
219
+ {
220
+ displayName: 'Timestamp',
221
+ name: 'ts',
222
+ type: 'number',
223
+ default: 0,
224
+ description: 'Timestamp (ms) của tin nhắn cần trích dẫn',
225
+ },
226
+ {
227
+ displayName: 'Message Type',
228
+ name: 'msgType',
229
+ type: 'number',
230
+ default: 1,
231
+ description: 'Loại tin nhắn được trích dẫn (ví dụ: 1 cho text)',
232
+ },
233
+ ],
234
+ },
235
+ ],
236
+ },
237
+ {
238
+ displayName: 'Mentions',
239
+ name: 'mentions',
240
+ type: 'fixedCollection',
241
+ typeOptions: {
242
+ multipleValues: false,
243
+ },
244
+ placeholder: 'Add Mention',
245
+ default: {},
246
+ options: [
247
+ {
248
+ displayName: 'Mention Details',
249
+ name: 'mentionValue',
250
+ values: [
251
+ {
252
+ displayName: 'User ID',
253
+ name: 'uid',
254
+ type: 'string',
255
+ default: '',
256
+ description: 'ID của người dùng được mention',
257
+ },
258
+ {
259
+ displayName: 'Position',
260
+ name: 'pos',
261
+ type: 'number',
262
+ default: 0,
263
+ description: 'Vị trí mention trong tin nhắn',
264
+ },
265
+ {
266
+ displayName: 'Length',
267
+ name: 'len',
268
+ type: 'number',
269
+ default: 0,
270
+ description: 'Độ dài của mention',
271
+ },
272
+ ],
273
+ },
274
+ ],
275
+ },
276
+ {
277
+ displayName: 'Text Styles',
278
+ name: 'styles',
279
+ type: 'fixedCollection',
280
+ typeOptions: {
281
+ multipleValues: true,
282
+ },
283
+ placeholder: 'Add Text Style',
284
+ default: {},
285
+ options: [
286
+ {
287
+ name: 'style',
288
+ displayName: 'Style',
289
+ values: [
290
+ {
291
+ displayName: 'Style Type',
292
+ name: 'st',
293
+ type: 'options',
294
+ options: [
295
+ {
296
+ name: 'Big Font',
297
+ value: 'f_18',
298
+ },
299
+ {
300
+ name: 'Bold',
301
+ value: 'b',
302
+ },
303
+ {
304
+ name: 'Green Color',
305
+ value: 'c_15a85f',
306
+ },
307
+ {
308
+ name: 'Indent',
309
+ value: 'ind_$',
310
+ },
311
+ {
312
+ name: 'Italic',
313
+ value: 'i',
314
+ },
315
+ {
316
+ name: 'Orange Color',
317
+ value: 'c_f27806',
318
+ },
319
+ {
320
+ name: 'Ordered List',
321
+ value: 'lst_2',
322
+ },
323
+ {
324
+ name: 'Red Color',
325
+ value: 'c_db342e',
326
+ },
327
+ {
328
+ name: 'Small Font',
329
+ value: 'f_13',
330
+ },
331
+ {
332
+ name: 'Strike Through',
333
+ value: 's',
334
+ },
335
+ {
336
+ name: 'Underline',
337
+ value: 'u',
338
+ },
339
+ {
340
+ name: 'Unordered List',
341
+ value: 'lst_1',
342
+ },
343
+ {
344
+ name: 'Yellow Color',
345
+ value: 'c_f7b503',
346
+ },
347
+ ],
348
+ default: 'b',
349
+ description: 'Loại style áp dụng cho text',
350
+ },
351
+ {
352
+ displayName: 'Start Position',
353
+ name: 'start',
354
+ type: 'number',
355
+ default: 0,
356
+ description: 'Vị trí bắt đầu áp dụng style (tính từ 0)',
357
+ },
358
+ {
359
+ displayName: 'End Position',
360
+ name: 'end',
361
+ type: 'number',
362
+ default: 0,
363
+ description: 'Vị trí kết thúc áp dụng style',
364
+ },
365
+ {
366
+ displayName: 'Indent Size',
367
+ name: 'indentSize',
368
+ type: 'number',
369
+ default: 1,
370
+ displayOptions: {
371
+ show: {
372
+ 'st': ['ind_$'],
373
+ },
374
+ },
375
+ description: 'Kích thước indent (chỉ dùng cho Indent style)',
376
+ },
377
+ ],
378
+ },
379
+ ],
380
+ description: 'Định dạng text với các style khác nhau',
381
+ },
382
+ {
383
+ displayName: 'TTL (Time To Live)',
384
+ name: 'ttl',
385
+ type: 'number',
386
+ default: 0,
387
+ description: 'Thời gian tồn tại của tin nhắn (giây). 0 = vĩnh viễn.',
388
+ },
389
+ ];
@@ -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
- // Start listening
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
- if (telegramToken && telegramChatId) {
501
- 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)`;
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,
@@ -7,6 +7,7 @@ exports.ZaloUploadAttachment = void 0;
7
7
  const n8n_workflow_1 = require("n8n-workflow");
8
8
  const zca_js_1 = require("zca-js");
9
9
  const helper_1 = require("../utils/helper");
10
+ const zalo_helper_1 = require("../utils/zalo.helper");
10
11
  const fs_1 = __importDefault(require("fs"));
11
12
  let api;
12
13
  class ZaloUploadAttachment {
@@ -149,34 +150,16 @@ class ZaloUploadAttachment {
149
150
  async execute() {
150
151
  const returnData = [];
151
152
  const items = this.getInputData();
152
- const zaloCred = await this.getCredentials('zaloApi');
153
- const cookieFromCred = JSON.parse(zaloCred.cookie);
154
- const imeiFromCred = zaloCred.imei;
155
- const userAgentFromCred = zaloCred.userAgent;
156
153
  try {
157
- const zalo = new zca_js_1.Zalo({
158
- selfListen: false,
159
- logging: true,
160
- imageMetadataGetter: async (filePath) => {
161
- return {
162
- width: 0,
163
- height: 0,
164
- size: 0
165
- };
166
- }
167
- });
168
- api = await zalo.login({
169
- cookie: cookieFromCred,
170
- imei: imeiFromCred,
171
- userAgent: userAgentFromCred
172
- });
154
+ api = await (0, zalo_helper_1.getZaloApiClient)(this, { needsImageMetadataGetter: true, selfListen: true });
173
155
  if (!api) {
174
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to initialize Zalo API. Check your credentials.');
156
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to initialize Zalo API. Check credentials or User ID.');
175
157
  }
176
158
  }
177
159
  catch (error) {
178
160
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Zalo login error: ${error.message}`);
179
161
  }
162
+
180
163
  for (let i = 0; i < items.length; i++) {
181
164
  try {
182
165
  const threadId = this.getNodeParameter('threadId', i);
@@ -188,12 +171,29 @@ class ZaloUploadAttachment {
188
171
  }
189
172
  const sources = [];
190
173
  const tempFiles = [];
174
+
175
+ const path_1 = __importDefault(require("path"));
176
+ const os_1 = __importDefault(require("os"));
177
+ const n8nUserFolder = process.env.N8N_USER_FOLDER || path_1.default.join(os_1.default.homedir(), '.n8n');
178
+
191
179
  for (const sourceConfig of sourcesData.source) {
192
180
  let source;
193
181
  if (sourceConfig.type === 'filePath') {
194
- const filePath = sourceConfig.filePath;
182
+ let filePath = sourceConfig.filePath;
195
183
  if (!fs_1.default.existsSync(filePath)) {
196
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `File không tồn tại: ${filePath}`);
184
+ const relativePath = path_1.default.join(n8nUserFolder, filePath);
185
+ const tempPath = path_1.default.join(n8nUserFolder, 'temp_files', path_1.default.basename(filePath));
186
+
187
+ if (fs_1.default.existsSync(relativePath)) {
188
+ filePath = relativePath;
189
+ this.logger.info(`Resolved relative path: ${filePath}`);
190
+ } else if (fs_1.default.existsSync(tempPath)) {
191
+ filePath = tempPath;
192
+ this.logger.info(`Resolved temp path: ${filePath}`);
193
+ } else {
194
+ this.logger.error(`File path not found: ${sourceConfig.filePath}`);
195
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `File không tồn tại: ${sourceConfig.filePath}. (Đã thử tìm tại: ${relativePath})`);
196
+ }
197
197
  }
198
198
  source = filePath;
199
199
  }
@@ -203,6 +203,7 @@ class ZaloUploadAttachment {
203
203
  if (!tempFilePath) {
204
204
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Không thể tải file từ URL: ${fileUrl}`);
205
205
  }
206
+ this.logger.info(`Downloaded URL to temp file: ${tempFilePath}`);
206
207
  tempFiles.push(tempFilePath);
207
208
  source = tempFilePath;
208
209
  }
@@ -226,9 +227,6 @@ class ZaloUploadAttachment {
226
227
  sources.push(source);
227
228
  }
228
229
  this.logger.info(`Uploading ${sources.length} file(s) to thread ${threadId} (${threadTypeStr})`);
229
- if (!api) {
230
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Zalo API not initialized');
231
- }
232
230
  const uploadResults = await api.uploadAttachment(sources, threadId, threadType);
233
231
  for (const tempFile of tempFiles) {
234
232
  (0, helper_1.removeFile)(tempFile);
@@ -53,7 +53,7 @@ exports.ZaloUploadAttachmentDescription = {
53
53
  {
54
54
  name: 'File Path',
55
55
  value: 'filePath',
56
- description: 'Đường dẫn file trên server n8n',
56
+ description: 'Đường dẫn file (nên lưu tại: /home/node/.n8n/temp_files)',
57
57
  },
58
58
  {
59
59
  name: 'URL',