n8n-nodes-zalo-custom 1.1.0 → 1.1.1

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.
@@ -1,57 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.decrypt = exports.encrypt = void 0;
27
- const crypto = __importStar(require("crypto"));
28
- const ALGORITHM = 'aes-256-gcm';
29
- const IV_LENGTH = 16;
30
- const SALT_LENGTH = 64;
31
- const TAG_LENGTH = 16;
32
- const KEY_LENGTH = 32;
33
- const PBKDF2_ITERATIONS = 100000;
34
- function getKey(encryptionKey) {
35
- return crypto.scryptSync(encryptionKey, 'salt', KEY_LENGTH);
36
- }
37
- function encrypt(data, encryptionKey) {
38
- const key = getKey(encryptionKey);
39
- const iv = crypto.randomBytes(IV_LENGTH);
40
- const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
41
- const encrypted = Buffer.concat([cipher.update(JSON.stringify(data), 'utf8'), cipher.final()]);
42
- const tag = cipher.getAuthTag();
43
- return Buffer.concat([iv, tag, encrypted]).toString('hex');
44
- }
45
- exports.encrypt = encrypt;
46
- function decrypt(encryptedHex, encryptionKey) {
47
- const key = getKey(encryptionKey);
48
- const encryptedBuffer = Buffer.from(encryptedHex, 'hex');
49
- const iv = encryptedBuffer.slice(0, IV_LENGTH);
50
- const tag = encryptedBuffer.slice(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
51
- const encrypted = encryptedBuffer.slice(IV_LENGTH + TAG_LENGTH);
52
- const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
53
- decipher.setAuthTag(tag);
54
- const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
55
- return JSON.parse(decrypted.toString('utf8'));
56
- }
57
- exports.decrypt = decrypt;
@@ -1,189 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.saveFile = saveFile;
7
- exports.removeFile = removeFile;
8
- exports.parseHtmlToStyles = parseHtmlToStyles;
9
- const axios_1 = __importDefault(require("axios"));
10
- const fs_1 = __importDefault(require("fs"));
11
- const os_1 = __importDefault(require("os"));
12
- const path_1 = __importDefault(require("path"));
13
- async function saveFile(url) {
14
- try {
15
- const n8nUserFolder = process.env.N8N_USER_FOLDER || path_1.default.join(os_1.default.homedir(), '.n8n');
16
- const dataStoragePath = path_1.default.join(n8nUserFolder, 'temp_files');
17
- if (!fs_1.default.existsSync(dataStoragePath)) {
18
- fs_1.default.mkdirSync(dataStoragePath, { recursive: true });
19
- }
20
- const urlPath = new URL(url).pathname;
21
- const ext = path_1.default.extname(urlPath) || '.bin';
22
- const timestamp = Date.now();
23
- const filePath = path_1.default.join(dataStoragePath, `temp-${timestamp}${ext}`);
24
- const { data } = await axios_1.default.get(url, { responseType: 'arraybuffer' });
25
- fs_1.default.writeFileSync(filePath, data);
26
- return filePath;
27
- }
28
- catch (error) {
29
- console.error('Lỗi khi tải/lưu file:', error);
30
- return null;
31
- }
32
- }
33
- function removeFile(filePath) {
34
- try {
35
- if (fs_1.default.existsSync(filePath)) {
36
- fs_1.default.unlinkSync(filePath);
37
- }
38
- }
39
- catch (error) {
40
- console.error('Lỗi khi xoá file:', error);
41
- }
42
- }
43
- function parseHtmlToStyles(html) {
44
- var _a, _b;
45
- let workingHtml = html
46
- .replace(/"/g, '"')
47
- .replace(/'/g, "'")
48
- .replace(/&lt;/g, '<')
49
- .replace(/&gt;/g, '>')
50
- .replace(/&amp;/g, '&')
51
- .replace(/&nbsp;/g, ' ');
52
- workingHtml = workingHtml.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, '$2');
53
- workingHtml = workingHtml.replace(/<br\s*\/?>/gi, '___BR___');
54
- workingHtml = workingHtml.replace(/>\s+</g, '><');
55
- workingHtml = workingHtml.replace(/___BR___/g, '<br>');
56
- const styles = [];
57
- const tagMappings = {
58
- 'b': 'b',
59
- 'strong': 'b',
60
- 'i': 'i',
61
- 'em': 'i',
62
- 'u': 'u',
63
- 's': 's',
64
- 'strike': 's',
65
- 'del': 's',
66
- 'small': 'f_13',
67
- 'big': 'f_18',
68
- 'red': 'c_db342e',
69
- 'orange': 'c_f27806',
70
- 'yellow': 'c_f7b503',
71
- 'green': 'c_15a85f',
72
- };
73
- const openTags = [];
74
- let cleanText = '';
75
- let htmlIndex = 0;
76
- let textIndex = 0;
77
- while (htmlIndex < workingHtml.length) {
78
- const char = workingHtml[htmlIndex];
79
- if (char === '<') {
80
- const tagEndIndex = workingHtml.indexOf('>', htmlIndex);
81
- if (tagEndIndex === -1) {
82
- cleanText += char;
83
- textIndex++;
84
- htmlIndex++;
85
- continue;
86
- }
87
- const tagContent = workingHtml.substring(htmlIndex + 1, tagEndIndex);
88
- const isClosingTag = tagContent.startsWith('/');
89
- const tagName = (isClosingTag ? tagContent.substring(1) : tagContent.split(/\s/)[0]).toLowerCase();
90
- if (tagName === 'br') {
91
- cleanText += '\n';
92
- textIndex++;
93
- }
94
- else if (tagName === 'p') {
95
- if (isClosingTag) {
96
- cleanText += '\n\n';
97
- textIndex += 2;
98
- }
99
- }
100
- else if (tagName === 'div') {
101
- if (isClosingTag) {
102
- cleanText += '\n';
103
- textIndex++;
104
- }
105
- }
106
- else if (tagName.match(/^h[1-6]$/)) {
107
- if (isClosingTag) {
108
- cleanText += '\n';
109
- textIndex++;
110
- }
111
- }
112
- else if (tagName === 'li') {
113
- if (!isClosingTag) {
114
- cleanText += '• ';
115
- textIndex += 2;
116
- }
117
- else {
118
- cleanText += '\n';
119
- textIndex++;
120
- }
121
- }
122
- else if (tagName === 'ul' || tagName === 'ol') {
123
- if (!isClosingTag) {
124
- cleanText += '\n';
125
- textIndex++;
126
- }
127
- else {
128
- cleanText += '\n';
129
- textIndex++;
130
- }
131
- }
132
- else {
133
- const styleType = tagMappings[tagName];
134
- if (styleType) {
135
- if (isClosingTag) {
136
- for (let i = openTags.length - 1; i >= 0; i--) {
137
- if (openTags[i].tagName === tagName) {
138
- const openTag = openTags[i];
139
- if (textIndex > openTag.startPos) {
140
- styles.push({
141
- st: openTag.styleType,
142
- start: openTag.startPos,
143
- end: textIndex
144
- });
145
- }
146
- openTags.splice(i, 1);
147
- break;
148
- }
149
- }
150
- }
151
- else {
152
- openTags.push({
153
- tagName: tagName,
154
- styleType: styleType,
155
- startPos: textIndex
156
- });
157
- }
158
- }
159
- }
160
- htmlIndex = tagEndIndex + 1;
161
- }
162
- else {
163
- cleanText += char;
164
- textIndex++;
165
- htmlIndex++;
166
- }
167
- }
168
- for (const openTag of openTags) {
169
- if (textIndex > openTag.startPos) {
170
- styles.push({
171
- st: openTag.styleType,
172
- start: openTag.startPos,
173
- end: textIndex
174
- });
175
- }
176
- }
177
- cleanText = cleanText
178
- .replace(/\n\s*\n\s*\n/g, '\n\n')
179
- .replace(/[ \t]+/g, ' ');
180
- const trimStart = ((_b = (_a = cleanText.match(/^\s*/)) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.length) || 0;
181
- cleanText = cleanText.replace(/^\s+|\s+$/g, '');
182
- if (trimStart > 0) {
183
- for (const style of styles) {
184
- style.start = Math.max(0, style.start - trimStart);
185
- style.end = Math.max(style.start, style.end - trimStart);
186
- }
187
- }
188
- return { cleanText, styles };
189
- }
@@ -1,304 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sendToTelegram = exports.deleteSessionByUserId = exports.updateSessionInfo = exports.saveOrUpdateSession = exports.getZaloApiClient = exports.imageMetadataGetter = void 0;
4
- const n8n_workflow_1 = require("n8n-workflow");
5
- const zca_js_1 = require("zca-js");
6
- const image_size_1 = require("image-size");
7
- const fs_1 = require("fs");
8
- const axios_1 = require("axios");
9
- const FormData = require("form-data");
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"));
13
- const crypto_helper_1 = require("./crypto.helper");
14
- const path_1 = require("path");
15
-
16
- let db;
17
-
18
- async function initDb() {
19
- if (db)
20
- return db;
21
- const userFolder = process.env.N8N_USER_FOLDER
22
- ? process.env.N8N_USER_FOLDER
23
- : path_1.join(process.env.HOME || process.env.USERPROFILE || '.', '.n8n');
24
- const tempDir = path_1.join(userFolder, 'temp_files');
25
- if (!fs_1.existsSync(tempDir)) {
26
- fs_1.mkdirSync(tempDir, { recursive: true });
27
- }
28
- const dbPath = path_1.join(tempDir, 'zalo_sessions.db');
29
- try {
30
- const filebuffer = fs_1.existsSync(dbPath) ? fs_1.readFileSync(dbPath) : null;
31
- const SQL = await (0, sql_js_1.default)();
32
- db = new SQL.Database(filebuffer);
33
- const createTableQuery = `
34
- CREATE TABLE IF NOT EXISTS zalo_sessions (
35
- user_id TEXT PRIMARY KEY,
36
- name TEXT,
37
- phone TEXT UNIQUE,
38
- credential_id TEXT,
39
- encrypted_data TEXT,
40
- updated_at TEXT
41
- );`;
42
- db.exec(createTableQuery);
43
- }
44
- catch (e) {
45
- console.error(`Failed to initialize database at ${dbPath}:`, e);
46
- throw e;
47
- }
48
- return db;
49
- }
50
-
51
- async function persistDb() {
52
- if (!db) return;
53
- try {
54
- const userFolder =
55
- process.env.N8N_USER_FOLDER ||
56
- path_1.join(process.env.HOME || process.env.USERPROFILE || '.', '.n8n');
57
- const tempDir = path_1.join(userFolder, 'temp_files');
58
- const dbPath = path_1.join(tempDir, 'zalo_sessions.db');
59
- const data = db.export();
60
- await fs_1.promises.writeFile(dbPath, Buffer.from(data));
61
- } catch (e) {
62
- console.error(`Failed to persist database to file:`, e);
63
- throw e;
64
- }
65
- }
66
-
67
- async function imageMetadataGetter(filePath) {
68
- try {
69
- const stats = await fs_1.promises.stat(filePath);
70
- try {
71
- const dimensions = (0, image_size_1.default)(filePath);
72
- return {
73
- height: dimensions.height,
74
- width: dimensions.width,
75
- size: stats.size,
76
- };
77
- } catch (err) {
78
- // Nếu không phải ảnh (ví dụ mp4), trả về size thực tế để API xử lý tiếp
79
- return { height: 0, width: 0, size: stats.size };
80
- }
81
- }
82
- catch (e) {
83
- if (e.code === 'ENOENT' || e.code === 'EACCES') {
84
- console.log(`Failed to access file at path: ${filePath}. Error: ${e.message}`);
85
- }
86
- else {
87
- console.log(`Could not get metadata for file at path: ${filePath}. It might not be a valid image file. Error: ${e.message}`);
88
- }
89
- return { height: 0, width: 0, size: 0 };
90
- }
91
- }
92
- exports.imageMetadataGetter = imageMetadataGetter;
93
-
94
- async function getZaloApiClient(node, options = {}) {
95
- const useSession = node.getNodeParameter('useSession', 0, false);
96
- const userId = node.getNodeParameter('connectToId', 0, '');
97
- const { needsImageMetadataGetter = false, selfListen = false, logging = false } = options;
98
- let cookie, imei, userAgent, sessionInfo, proxy = null, actualZaloId = '';
99
- if (useSession && userId) {
100
- try {
101
- await initDb();
102
- } catch (e) {
103
- 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.`);
104
- }
105
-
106
- try {
107
- const isPhoneNumber = /^\d{10}$/.test(userId);
108
- let stmt;
109
- try {
110
- if (isPhoneNumber) {
111
- stmt = db.prepare("SELECT user_id, encrypted_data FROM zalo_sessions WHERE phone = :phone");
112
- const result = stmt.getAsObject({ ':phone': userId });
113
- if (result && result.user_id) {
114
- actualZaloId = result.user_id;
115
- sessionInfo = { data: result.encrypted_data };
116
- }
117
- }
118
- else {
119
- stmt = db.prepare("SELECT encrypted_data FROM zalo_sessions WHERE user_id = :user_id");
120
- const result = stmt.getAsObject({ ':user_id': userId });
121
- actualZaloId = userId;
122
- if (result && result.encrypted_data) {
123
- sessionInfo = { data: result.encrypted_data };
124
- }
125
- }
126
- } finally {
127
- if (stmt) stmt.free();
128
- }
129
- if (sessionInfo && sessionInfo.data) {
130
- const encryptionKey = actualZaloId.repeat(3);
131
- try {
132
- const sessionData = (0, crypto_helper_1.decrypt)(sessionInfo.data, encryptionKey);
133
- cookie = sessionData.cookie;
134
- imei = sessionData.imei;
135
- userAgent = sessionData.userAgent;
136
- proxy = sessionData.proxy;
137
- }
138
- catch (e) {
139
- 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}`);
140
- }
141
- }
142
- else {
143
- const idType = isPhoneNumber ? 'Phone number' : 'Zalo User ID';
144
- throw new n8n_workflow_1.NodeOperationError(node.getNode(), `[SS] ${idType} "${userId}" was provided, but no matching session was found.`);
145
- }
146
- }
147
- catch (e) {
148
- if (e instanceof n8n_workflow_1.NodeOperationError) throw e;
149
- throw new n8n_workflow_1.NodeOperationError(node.getNode(), `[SS] An unexpected database error occurred: ${e.message}.`);
150
- }
151
- } else if (!useSession) {
152
- const zaloCred = await node.getCredentials('zaloApi');
153
- cookie = JSON.parse(zaloCred.cookie);
154
- imei = zaloCred.imei;
155
- userAgent = zaloCred.userAgent;
156
- proxy = zaloCred.proxy;
157
- }
158
-
159
- const zaloOptions = {
160
- logging,
161
- selfListen,
162
- settings: {
163
- features: {
164
- socket: {
165
- close_and_retry_codes: [1006, 1000, 3000, 3003],
166
- retries: {
167
- "1006": {
168
- max: 10,
169
- times: [5000, 10000, 30000],
170
- },
171
- },
172
- },
173
- },
174
- },
175
- };
176
- if (proxy) {
177
- zaloOptions.agent = new https_proxy_agent_1.HttpsProxyAgent(proxy);
178
- zaloOptions.polyfill = node_fetch_1.default;
179
- }
180
- if (needsImageMetadataGetter) {
181
- zaloOptions.imageMetadataGetter = imageMetadataGetter;
182
- }
183
- const zalo = new zca_js_1.Zalo(zaloOptions);
184
- return zalo.login({ cookie, imei, userAgent });
185
- }
186
- exports.getZaloApiClient = getZaloApiClient;
187
-
188
- async function saveOrUpdateSession(sessionDetails) {
189
- try {
190
- const { userId, name, phone, credentialId, encryptedData } = sessionDetails;
191
- await initDb();
192
- let oldCredentialId = null;
193
-
194
- const selectStmt = db.prepare('SELECT credential_id FROM zalo_sessions WHERE user_id = :user_id');
195
- const existing = selectStmt.getAsObject({ ':user_id': userId });
196
- selectStmt.free();
197
- if (existing) {
198
- oldCredentialId = existing.credential_id;
199
- }
200
-
201
- const stmt = db.prepare(`
202
- INSERT INTO zalo_sessions (user_id, name, phone, credential_id, encrypted_data, updated_at)
203
- VALUES (:user_id, :name, :phone, :credential_id, :encrypted_data, :updated_at)
204
- ON CONFLICT(user_id) DO UPDATE SET
205
- name = excluded.name,
206
- phone = excluded.phone,
207
- credential_id = excluded.credential_id,
208
- encrypted_data = excluded.encrypted_data,
209
- updated_at = excluded.updated_at;
210
- `);
211
- stmt.run({
212
- ':user_id': userId,
213
- ':name': name,
214
- ':phone': phone,
215
- ':credential_id': credentialId,
216
- ':encrypted_data': encryptedData,
217
- ':updated_at': new Date().toISOString(),
218
- });
219
- stmt.free();
220
-
221
- await persistDb();
222
-
223
- return { oldCredentialId };
224
- } catch (e) {
225
- console.error(`[saveOrUpdateSession] Database operation failed:`, e); // eslint-disable-line no-console
226
- throw e;
227
- }
228
- }
229
- exports.saveOrUpdateSession = saveOrUpdateSession;
230
-
231
- async function updateSessionInfo(userId, name, phone) {
232
- try {
233
- await initDb();
234
- const stmt = db.prepare(`
235
- UPDATE zalo_sessions
236
- SET
237
- name = :name,
238
- phone = :phone,
239
- updated_at = :updated_at
240
- WHERE user_id = :user_id;
241
- `);
242
- stmt.run({
243
- ':user_id': userId,
244
- ':name': name,
245
- ':phone': phone,
246
- ':updated_at': new Date().toISOString(),
247
- });
248
- stmt.free();
249
- await persistDb();
250
- } catch (e) {
251
- console.error(`[updateSessionInfo] Database operation failed for user ${userId}:`, e);
252
- throw e;
253
- }
254
- }
255
- exports.updateSessionInfo = updateSessionInfo;
256
-
257
- async function deleteSessionByUserId(userId) {
258
- try {
259
- await initDb();
260
- const stmt = db.prepare('DELETE FROM zalo_sessions WHERE user_id = :user_id');
261
- stmt.run({ ':user_id': userId });
262
- stmt.free();
263
- await persistDb();
264
- } catch (e) {
265
- console.error(`[deleteSessionByUserId] Database operation failed for user ${userId}:`, e);
266
- throw e;
267
- }
268
- }
269
- exports.deleteSessionByUserId = deleteSessionByUserId;
270
-
271
- async function sendToTelegram({ token, chatId, text, binaryData, fileName, caption, logger, }) {
272
- if (!token || !chatId) {
273
- logger === null || logger === void 0 ? void 0 : logger.warn('Telegram token và chatId là bắt buộc');
274
- return;
275
- }
276
- const baseUrl = `https://api.telegram.org/bot${token}`;
277
- try {
278
- if (binaryData && fileName) {
279
- const form = new FormData();
280
- form.append('chat_id', chatId);
281
- form.append('photo', binaryData, fileName);
282
- if (caption) {
283
- form.append('caption', caption);
284
- }
285
- const url = `${baseUrl}/sendPhoto`;
286
- await axios_1.default.post(url, form, { headers: form.getHeaders() });
287
- logger === null || logger === void 0 ? void 0 : logger.info(` ..sendPhoto Telegram: ${caption}`);
288
- }
289
- else if (text) {
290
- const url = `${baseUrl}/sendMessage`;
291
- await axios_1.default.post(url, {
292
- chat_id: chatId,
293
- text: text,
294
- });
295
- logger === null || logger === void 0 ? void 0 : logger.info(` ..sendMessage Telegram: ${text}`);
296
- }
297
- }
298
- catch (error) {
299
- const errorMessage = error.response ? JSON.stringify(error.response.data) : error.message;
300
- logger === null || logger === void 0 ? void 0 : logger.error(` ..Fail send Telegram: ${errorMessage}`);
301
- throw new Error(`Gửi đến Telegram thất bại: ${errorMessage}`);
302
- }
303
- }
304
- exports.sendToTelegram = sendToTelegram;