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.

317 lines 13.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.isFFmpegAvailable = isFFmpegAvailable; exports.isFFprobeAvailable = isFFprobeAvailable; exports.downloadVideo = downloadVideo; exports.getVideoMetadata = getVideoMetadata; exports.extractThumbnail = extractThumbnail; exports.processVideo = processVideo; exports.cleanupVideoFiles = cleanupVideoFiles; exports.uploadThumbnailToCatbox = uploadThumbnailToCatbox; exports.uploadThumbnailToLitterbox = uploadThumbnailToLitterbox; exports.getPublicThumbnailUrl = getPublicThumbnailUrl; const path = __importStar(require("path")); const fs = __importStar(require("fs")); const os = __importStar(require("os")); const child_process_1 = require("child_process"); const util_1 = require("util"); const axios_1 = __importDefault(require("axios")); const execAsync = (0, util_1.promisify)(child_process_1.exec); function getTempDir() { const n8nUserFolder = process.env.N8N_USER_FOLDER || path.join(os.homedir(), '.n8n'); const tempDir = path.join(n8nUserFolder, 'temp_videos'); if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }); } return tempDir; } function isFFmpegAvailable() { try { (0, child_process_1.execSync)('ffmpeg -version', { stdio: 'ignore' }); return true; } catch { return false; } } function isFFprobeAvailable() { try { (0, child_process_1.execSync)('ffprobe -version', { stdio: 'ignore' }); return true; } catch { return false; } } async function downloadVideo(url, logger) { try { logger.info(`[VideoProcessor] Downloading video from: ${url}`); const tempDir = getTempDir(); const timestamp = Date.now(); let ext = '.mp4'; try { const parsedUrl = new URL(url); const urlPath = parsedUrl.pathname; const urlExt = path.extname(urlPath).toLowerCase(); if (urlExt && ['.mp4', '.mov', '.avi', '.webm', '.mkv'].includes(urlExt)) { ext = urlExt; } } catch { } const videoPath = path.join(tempDir, `video-${timestamp}${ext}`); const response = await axios_1.default.get(url, { responseType: 'arraybuffer', timeout: 120000, maxContentLength: 100 * 1024 * 1024, }); fs.writeFileSync(videoPath, response.data); const fileSize = fs.statSync(videoPath).size; logger.info(`[VideoProcessor] Video downloaded: ${videoPath} (${(fileSize / 1024 / 1024).toFixed(2)} MB)`); return videoPath; } catch (error) { logger.error(`[VideoProcessor] Failed to download video: ${error.message}`); return null; } } async function getVideoMetadata(videoPath, logger) { var _a, _b, _c; if (!isFFprobeAvailable()) { logger.warn('[VideoProcessor] ffprobe not available, cannot get video metadata'); return null; } try { const cmd = `ffprobe -v quiet -print_format json -show_format -show_streams "${videoPath}"`; const { stdout } = await execAsync(cmd); const probeData = JSON.parse(stdout); const videoStream = (_a = probeData.streams) === null || _a === void 0 ? void 0 : _a.find((s) => s.codec_type === 'video'); const duration = ((_b = probeData.format) === null || _b === void 0 ? void 0 : _b.duration) ? Math.round(parseFloat(probeData.format.duration) * 1000) : 0; const width = (videoStream === null || videoStream === void 0 ? void 0 : videoStream.width) || 1280; const height = (videoStream === null || videoStream === void 0 ? void 0 : videoStream.height) || 720; const fileSize = ((_c = probeData.format) === null || _c === void 0 ? void 0 : _c.size) ? parseInt(probeData.format.size) : 0; logger.info(`[VideoProcessor] Video metadata: ${width}x${height}, duration=${duration}ms, size=${fileSize}`); return { duration, width, height, fileSize }; } catch (error) { logger.error(`[VideoProcessor] Failed to get video metadata: ${error.message}`); return null; } } async function extractThumbnail(videoPath, logger) { if (!isFFmpegAvailable()) { logger.warn('[VideoProcessor] ffmpeg not available, cannot extract thumbnail'); return null; } try { const tempDir = getTempDir(); const timestamp = Date.now(); const thumbnailPath = path.join(tempDir, `thumb-${timestamp}.jpg`); const cmd = `ffmpeg -ss 1 -i "${videoPath}" -vframes 1 -q:v 2 -y "${thumbnailPath}"`; logger.info(`[VideoProcessor] Extracting thumbnail with command: ${cmd}`); await execAsync(cmd, { timeout: 30000 }); if (fs.existsSync(thumbnailPath)) { const thumbSize = fs.statSync(thumbnailPath).size; logger.info(`[VideoProcessor] Thumbnail extracted: ${thumbnailPath} (${thumbSize} bytes)`); return thumbnailPath; } else { const cmdFirstFrame = `ffmpeg -i "${videoPath}" -vframes 1 -q:v 2 -y "${thumbnailPath}"`; await execAsync(cmdFirstFrame, { timeout: 30000 }); if (fs.existsSync(thumbnailPath)) { logger.info(`[VideoProcessor] Thumbnail extracted (first frame): ${thumbnailPath}`); return thumbnailPath; } } logger.error('[VideoProcessor] Failed to extract thumbnail - file not created'); return null; } catch (error) { logger.error(`[VideoProcessor] Failed to extract thumbnail: ${error.message}`); return null; } } async function processVideo(videoSource, logger) { const result = { thumbnailPath: null, metadata: null, videoPath: null, errors: [], }; const ffmpegAvailable = isFFmpegAvailable(); const ffprobeAvailable = isFFprobeAvailable(); if (!ffmpegAvailable) { result.errors.push('ffmpeg not installed - cannot extract thumbnail automatically'); logger.warn('[VideoProcessor] ffmpeg not available'); } if (!ffprobeAvailable) { result.errors.push('ffprobe not installed - cannot get video metadata automatically'); logger.warn('[VideoProcessor] ffprobe not available'); } if (!ffmpegAvailable && !ffprobeAvailable) { return result; } const isLocalFile = fs.existsSync(videoSource); let videoPath = null; if (isLocalFile) { logger.info(`[VideoProcessor] Using local file: ${videoSource}`); videoPath = videoSource; result.videoPath = null; } else { videoPath = await downloadVideo(videoSource, logger); if (!videoPath) { result.errors.push('Failed to download video'); return result; } result.videoPath = videoPath; } if (ffprobeAvailable && videoPath) { result.metadata = await getVideoMetadata(videoPath, logger); } if (ffmpegAvailable && videoPath) { result.thumbnailPath = await extractThumbnail(videoPath, logger); } return result; } function cleanupVideoFiles(files, logger) { for (const filePath of files) { if (filePath && fs.existsSync(filePath)) { try { fs.unlinkSync(filePath); logger.info(`[VideoProcessor] Cleaned up: ${filePath}`); } catch (error) { logger.warn(`[VideoProcessor] Failed to cleanup ${filePath}: ${error.message}`); } } } } async function uploadThumbnailToCatbox(localPath, logger) { try { const FormData = (await Promise.resolve().then(() => __importStar(require('form-data')))).default; logger.info(`[VideoProcessor] Uploading thumbnail to catbox.moe: ${localPath}`); const formData = new FormData(); formData.append('reqtype', 'fileupload'); formData.append('fileToUpload', fs.createReadStream(localPath)); const response = await axios_1.default.post('https://catbox.moe/user/api.php', formData, { headers: formData.getHeaders(), timeout: 60000, }); const uploadedUrl = response.data; if (uploadedUrl && typeof uploadedUrl === 'string' && uploadedUrl.startsWith('https://')) { logger.info(`[VideoProcessor] Thumbnail uploaded successfully: ${uploadedUrl}`); return uploadedUrl; } logger.error(`[VideoProcessor] Unexpected catbox response: ${uploadedUrl}`); return null; } catch (error) { logger.error(`[VideoProcessor] Failed to upload thumbnail to catbox: ${error.message}`); return null; } } async function uploadThumbnailToLitterbox(localPath, logger) { try { const FormData = (await Promise.resolve().then(() => __importStar(require('form-data')))).default; logger.info(`[VideoProcessor] Uploading thumbnail to litterbox (temp): ${localPath}`); const formData = new FormData(); formData.append('reqtype', 'fileupload'); formData.append('time', '1h'); formData.append('fileToUpload', fs.createReadStream(localPath)); const response = await axios_1.default.post('https://litterbox.catbox.moe/resources/internals/api.php', formData, { headers: formData.getHeaders(), timeout: 60000, }); const uploadedUrl = response.data; if (uploadedUrl && typeof uploadedUrl === 'string' && uploadedUrl.startsWith('https://')) { logger.info(`[VideoProcessor] Thumbnail uploaded to litterbox: ${uploadedUrl}`); return uploadedUrl; } logger.error(`[VideoProcessor] Unexpected litterbox response: ${uploadedUrl}`); return null; } catch (error) { logger.error(`[VideoProcessor] Failed to upload to litterbox: ${error.message}`); return uploadThumbnailToCatbox(localPath, logger); } } async function getPublicThumbnailUrl(videoUrl, userProvidedUrl, logger) { const result = { url: null, localPath: null, videoPath: null, metadata: null, diagnosis: null, }; if (userProvidedUrl && userProvidedUrl.trim() !== '') { result.url = userProvidedUrl.trim(); logger.info(`[VideoProcessor] Using user-provided thumbnail URL: ${result.url}`); return result; } logger.info('[VideoProcessor] No thumbnail provided, attempting to extract from video...'); const processed = await processVideo(videoUrl, logger); result.videoPath = processed.videoPath; result.metadata = processed.metadata; if (!processed.thumbnailPath) { result.diagnosis = processed.errors.length > 0 ? `Could not extract a thumbnail from the video: ${processed.errors.join('; ')}` : 'Could not extract a thumbnail from the video (unknown reason — check server logs).'; logger.error(`[VideoProcessor] ${result.diagnosis}`); return result; } result.localPath = processed.thumbnailPath; const uploadedUrl = await uploadThumbnailToLitterbox(processed.thumbnailPath, logger); if (uploadedUrl) { result.url = uploadedUrl; return result; } result.diagnosis = 'A thumbnail was extracted from the video successfully with ffmpeg, but uploading it to ' + 'the public image host failed (litterbox.catbox.moe and catbox.moe both rejected the ' + 'upload). These free anonymous hosts often block requests from cloud/VPS server IPs, ' + 'which is the common case for self-hosted n8n. Provide a "Thumbnail URL" manually ' + '(e.g. host it on your own storage/CDN) to avoid depending on them.'; logger.warn(`[VideoProcessor] ${result.diagnosis}`); return result; } //# sourceMappingURL=videoProcessor.js.map