n8n-nodes-zalo-custom 1.0.10 → 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
+ ];
@@ -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',
@@ -6,6 +6,7 @@ const n8n_workflow_1 = require("n8n-workflow");
6
6
  const ZaloNodeProperties_1 = require("../shared/ZaloNodeProperties");
7
7
  const ZaloUserDescription_1 = require("./ZaloUserDescription");
8
8
  const zalo_helper_1 = require("../utils/zalo.helper");
9
+ const helper_1 = require("../utils/helper");
9
10
  let api;
10
11
  class ZaloUser {
11
12
  constructor() {
@@ -47,8 +48,14 @@ class ZaloUser {
47
48
  const items = this.getInputData();
48
49
  const returnData = [];
49
50
 
51
+ const needsImageMetadataGetter = items.some((_, i) => {
52
+ try {
53
+ return this.getNodeParameter('operation', i) === 'changeAccountAvatar';
54
+ } catch (e) { return false; }
55
+ });
56
+
50
57
  try {
51
- api = await (0, zalo_helper_1.getZaloApiClient)(this, {});
58
+ api = await (0, zalo_helper_1.getZaloApiClient)(this, { needsImageMetadataGetter });
52
59
  if (!api) {
53
60
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to initialize Zalo API. Check credentials or User ID.');
54
61
  }
@@ -265,6 +272,93 @@ class ZaloUser {
265
272
  returnData.push({ json: { success: true, ...processedResponse }, pairedItem: { item: i } });
266
273
  break;
267
274
  }
275
+ case 'setMute': {
276
+ const threadId = this.getNodeParameter('threadId', i);
277
+ const muteAction = this.getNodeParameter('muteAction', i);
278
+ const type = this.getNodeParameter('threadType', i);
279
+ let duration = -1;
280
+ if (muteAction === 1) {
281
+ const durationParam = this.getNodeParameter('muteDuration', i);
282
+ if (durationParam === -2) {
283
+ duration = this.getNodeParameter('customMuteDuration', i) * 60;
284
+ console.log(duration)
285
+ } else {
286
+ duration = durationParam;
287
+ }
288
+ }
289
+ const params = {
290
+ action: muteAction,
291
+ duration: duration,
292
+ };
293
+ const response = await api.setMute(params, threadId, type);
294
+ returnData.push({ json: { success: true, response }, pairedItem: { item: i } });
295
+ break;
296
+ }
297
+ case 'deleteMessage': {
298
+ const threadId = this.getNodeParameter('threadId', i);
299
+ const uidFrom = this.getNodeParameter('userId', i);
300
+ const msgId = this.getNodeParameter('msgId', i);
301
+ const cliMsgId = this.getNodeParameter('cliMsgId', i);
302
+ const onlyMe = this.getNodeParameter('onlyMe', i, true);
303
+ const type = this.getNodeParameter('threadType', i);
304
+ const dest = {
305
+ data: { cliMsgId, msgId, uidFrom },
306
+ threadId: threadId,
307
+ type: type,
308
+ };
309
+ const response = await api.deleteMessage(dest, onlyMe);
310
+ returnData.push({ json: { success: true, response }, pairedItem: { item: i } });
311
+ break;
312
+ }
313
+ case 'updateSettings': {
314
+ const settings = this.getNodeParameter('updateSettingsOptions', i);
315
+ const results = {};
316
+ const keys = Object.keys(settings);
317
+ for (let k = 0; k < keys.length; k++) {
318
+ const key = keys[k];
319
+ const value = settings[key];
320
+ try {
321
+ const response = await api.updateSettings(key, value);
322
+ results[key] = { success: true, response };
323
+ } catch (error) {
324
+ results[key] = { success: false, error: error.message };
325
+ }
326
+ if (k < keys.length - 1) {
327
+ await new Promise((resolve) => setTimeout(resolve, 1000));
328
+ }
329
+ }
330
+ returnData.push({ json: { success: true, results }, pairedItem: { item: i } });
331
+ break;
332
+ }
333
+ case 'changeAccountAvatar': {
334
+ const imageUrl = this.getNodeParameter('imageUrl', i);
335
+ let finalAvatarSource;
336
+ let tempFilePath;
337
+ try {
338
+ if (!imageUrl) {
339
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Image URL is required for changing account avatar.', { itemIndex: i });
340
+ }
341
+ try {
342
+ new URL(imageUrl);
343
+ this.logger.info(`Downloading image from URL for avatar: ${imageUrl}`);
344
+ tempFilePath = await (0, helper_1.saveFile)(imageUrl);
345
+ finalAvatarSource = tempFilePath;
346
+ }
347
+ catch (urlError) {
348
+ this.logger.info(`Treating image URL as local file path for avatar: ${imageUrl}`);
349
+ finalAvatarSource = imageUrl;
350
+ }
351
+ const response = await api.changeAccountAvatar(finalAvatarSource);
352
+ returnData.push({ json: { success: true, response }, pairedItem: { item: i } });
353
+ }
354
+ finally {
355
+ if (tempFilePath) {
356
+ await (0, helper_1.removeFile)(tempFilePath);
357
+ this.logger.info(`Successfully removed temporary avatar file: ${tempFilePath}`);
358
+ }
359
+ }
360
+ break;
361
+ }
268
362
  }
269
363
  }
270
364
  }