UNPKG

n8n-nodes-zalo-nnt

Version:

Unofficial Zalo integration for n8n - Send messages, manage groups, user operations with QR login. No API key required, works via browser simulation.

250 lines 9.47 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.pickNextCursors = pickNextCursors; exports.getChatHistory = getChatHistory; const zca_js_1 = require("zca-js"); const types_1 = require("./types"); const messageParser_1 = require("./messageParser"); const zaloMessage_1 = require("../../utils/zaloMessage"); const DEFAULT_MAX_PAGES = 50; function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function toMillis(timestamp) { return typeof timestamp === 'string' ? parseInt(timestamp, 10) : timestamp; } function filterMessages(messages, params) { let result = messages; if (params.filterByThreadId !== false && params.threadId) { result = result.filter((msg) => msg.threadId === params.threadId); } if (params.fromDate || params.toDate) { result = result.filter((msg) => { const timestamp = toMillis(msg.timestamp); if (params.fromDate && timestamp < params.fromDate) { return false; } if (params.toDate && timestamp > params.toDate) { return false; } return true; }); } return result; } function sortMessages(messages, sortOrder) { const direction = sortOrder === 'asc' ? 1 : -1; return messages.sort((a, b) => (0, zaloMessage_1.compareBySortKey)((0, zaloMessage_1.messageSortKey)(a.msgId, a.timestamp), (0, zaloMessage_1.messageSortKey)(b.msgId, b.timestamp), direction)); } function pickNextCursors(messages) { const cursors = []; const seen = new Set(); const add = (candidate) => { const value = String(candidate !== null && candidate !== void 0 ? candidate : '').trim(); if (!value || seen.has(value)) return; seen.add(value); cursors.push(value); }; const cursorOf = (msg) => String((msg === null || msg === void 0 ? void 0 : msg.msgId) || (msg === null || msg === void 0 ? void 0 : msg.actionId) || (msg === null || msg === void 0 ? void 0 : msg.cliMsgId) || ''); if (messages.length === 0) { return cursors; } let oldest = messages[0]; for (const msg of messages) { if (toMillis(msg.timestamp) < toMillis(oldest.timestamp)) { oldest = msg; } } add(cursorOf(oldest)); add(cursorOf(messages[messages.length - 1])); add(cursorOf(messages[0])); return cursors; } function requestBatch(listener, zcaThreadType, lastMsgId, timeout, allowIdleFinish = false) { return new Promise((resolve, reject) => { let settled = false; let timeoutId; const cleanup = () => { if (timeoutId) { clearTimeout(timeoutId); } listener.removeAllListeners('old_messages'); listener.removeAllListeners('error'); }; const fail = (error) => { if (settled) return; settled = true; cleanup(); reject(error); }; const succeed = (messages) => { if (settled) return; settled = true; cleanup(); resolve(messages); }; timeoutId = setTimeout(() => { if (allowIdleFinish) { succeed([]); } else { fail(new Error(`Request timeout after ${timeout}ms`)); } }, timeout); listener.once('error', (error) => fail(error)); listener.once('old_messages', (messages, threadType) => { if (settled) return; try { const isGroup = threadType === zca_js_1.ThreadType.Group; succeed((0, messageParser_1.parseMessages)(messages, isGroup)); } catch (error) { fail(error); } }); try { listener.requestOldMessages(zcaThreadType, lastMsgId); } catch (error) { fail(error); } }); } async function getChatHistory(api, params, listener, timeout = 30000) { var _a; if (params.fetchAll) { return getChatHistoryAll(api, params, listener, timeout); } const zcaThreadType = params.threadType === types_1.ThreadType.User ? zca_js_1.ThreadType.User : zca_js_1.ThreadType.Group; const rawMessages = await requestBatch(listener, zcaThreadType, params.lastMsgId || null, timeout); const cursor = (_a = pickNextCursors(rawMessages)[0]) !== null && _a !== void 0 ? _a : null; const limit = params.limit || 20; const matched = sortMessages(filterMessages(rawMessages, params), params.sortOrder); const limitedMessages = matched.slice(0, limit); if (params.includeReactions && limitedMessages.length > 0) { await attachReactions(listener, limitedMessages, cursor || '', params.threadType === types_1.ThreadType.Group); } return { messages: limitedMessages, hasMore: cursor !== null, lastMsgId: cursor, totalCount: limitedMessages.length, fetchedCount: rawMessages.length, pagesRequested: 1, threadId: params.threadId, threadType: params.threadType, filterByThreadId: params.filterByThreadId !== false, }; } async function getChatHistoryAll(api, params, listener, timeout = 30000) { var _a; const allMessages = []; const requestedCursors = new Set(); const delayMs = params.delayBetweenRequests || 1000; const maxPages = params.maxPages && params.maxPages > 0 ? params.maxPages : DEFAULT_MAX_PAGES; const zcaThreadType = params.threadType === types_1.ThreadType.User ? zca_js_1.ThreadType.User : zca_js_1.ThreadType.Group; let currentCursor = params.lastMsgId || null; let lastCursor = currentCursor; let pagesRequested = 0; let exhausted = false; while (pagesRequested < maxPages) { if (currentCursor) { requestedCursors.add(currentCursor); } pagesRequested++; console.log(`Fetching batch ${pagesRequested}/${maxPages}, cursor: ${currentCursor || 'null'}`); const batch = await requestBatch(listener, zcaThreadType, currentCursor, timeout, pagesRequested > 1); if (batch.length === 0) { exhausted = true; break; } allMessages.push(...batch); const nextCursor = (_a = pickNextCursors(batch).find((c) => !requestedCursors.has(c))) !== null && _a !== void 0 ? _a : null; if (nextCursor === null) { exhausted = true; break; } currentCursor = nextCursor; lastCursor = nextCursor; await delay(delayMs); } if (!exhausted) { console.warn(`Đã chạm trần ${maxPages} trang — backlog có thể còn. ` + `Chạy lại với lastMsgId="${lastCursor}" để kéo tiếp.`); } const filteredMessages = sortMessages(filterMessages(allMessages, params), params.sortOrder); if (params.includeReactions && filteredMessages.length > 0) { await attachReactions(listener, filteredMessages, filteredMessages[filteredMessages.length - 1].msgId, params.threadType === types_1.ThreadType.Group); } return { messages: filteredMessages, hasMore: !exhausted, lastMsgId: lastCursor, totalCount: filteredMessages.length, fetchedCount: allMessages.length, pagesRequested, threadId: params.threadId, threadType: params.threadType, filterByThreadId: params.filterByThreadId !== false, }; } async function attachReactions(listener, messages, lastMsgId, isGroup) { try { const reactions = await getMessageReactions(listener, lastMsgId, isGroup, 5000); const reactionMap = new Map(); reactions.forEach((reaction) => { const msgReactions = reactionMap.get(reaction.msgId) || []; msgReactions.push(reaction); reactionMap.set(reaction.msgId, msgReactions); }); messages.forEach((msg) => { msg.reactions = reactionMap.get(msg.msgId); }); } catch (error) { console.warn('Failed to fetch reactions:', error); } } async function getMessageReactions(listener, lastMsgId, isGroup, timeout = 5000) { return new Promise((resolve) => { let resolved = false; let timeoutId; const cleanup = () => { if (timeoutId) { clearTimeout(timeoutId); } listener.removeAllListeners('old_reactions'); }; timeoutId = setTimeout(() => { if (!resolved) { resolved = true; cleanup(); resolve([]); } }, timeout); listener.once('old_reactions', (reactions) => { if (resolved) return; resolved = true; cleanup(); const parsed = reactions.map(messageParser_1.parseReaction); resolve(parsed); }); try { const threadType = isGroup ? zca_js_1.ThreadType.Group : zca_js_1.ThreadType.User; listener.requestOldReactions(threadType, lastMsgId); } catch (error) { if (!resolved) { resolved = true; cleanup(); resolve([]); } } }); } //# sourceMappingURL=historyProcessor.js.map